// source --> https://aristarh.rs/wp-content/mmr/81ee5dee-1652098085.js /* ------------------------------------------------------------------------ Class: prettyPhoto Use: Lightbox clone for jQuery Author: Stephane Caron (http://www.no-margin-for-errors.com) Version: 3.1.6 ------------------------------------------------------------------------- */ !function(e){function t(){var e=location.href;return hashtag=-1!==e.indexOf("#prettyPhoto")?decodeURI(e.substring(e.indexOf("#prettyPhoto")+1,e.length)):!1,hashtag&&(hashtag=hashtag.replace(/<|>/g,"")),hashtag}function i(){"undefined"!=typeof theRel&&(location.hash=theRel+"/"+rel_index+"/")}function p(){-1!==location.href.indexOf("#prettyPhoto")&&(location.hash="prettyPhoto")}function o(e,t){e=e.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]");var i="[\\?&]"+e+"=([^&#]*)",p=new RegExp(i),o=p.exec(t);return null==o?"":o[1]}e.prettyPhoto={version:"3.1.6"},e.fn.prettyPhoto=function(a){function s(){e(".pp_loaderIcon").hide(),projectedTop=scroll_pos.scrollTop+(I/2-f.containerHeight/2),projectedTop<0&&(projectedTop=0),$ppt.fadeTo(settings.animation_speed,1),$pp_pic_holder.find(".pp_content").animate({height:f.contentHeight,width:f.contentWidth},settings.animation_speed),$pp_pic_holder.animate({top:projectedTop,left:j/2-f.containerWidth/2<0?0:j/2-f.containerWidth/2,width:f.containerWidth},settings.animation_speed,function(){$pp_pic_holder.find(".pp_hoverContainer,#fullResImage").height(f.height).width(f.width),$pp_pic_holder.find(".pp_fade").fadeIn(settings.animation_speed),isSet&&"image"==h(pp_images[set_position])?$pp_pic_holder.find(".pp_hoverContainer").show():$pp_pic_holder.find(".pp_hoverContainer").hide(),settings.allow_expand&&(f.resized?e("a.pp_expand,a.pp_contract").show():e("a.pp_expand").hide()),!settings.autoplay_slideshow||P||v||e.prettyPhoto.startSlideshow(),settings.changepicturecallback(),v=!0}),m(),a.ajaxcallback()}function n(t){$pp_pic_holder.find("#pp_full_res object,#pp_full_res embed").css("visibility","hidden"),$pp_pic_holder.find(".pp_fade").fadeOut(settings.animation_speed,function(){e(".pp_loaderIcon").show(),t()})}function r(t){t>1?e(".pp_nav").show():e(".pp_nav").hide()}function l(e,t){if(resized=!1,d(e,t),imageWidth=e,imageHeight=t,(k>j||b>I)&&doresize&&settings.allow_resize&&!$){for(resized=!0,fitting=!1;!fitting;)k>j?(imageWidth=j-200,imageHeight=t/e*imageWidth):b>I?(imageHeight=I-200,imageWidth=e/t*imageHeight):fitting=!0,b=imageHeight,k=imageWidth;(k>j||b>I)&&l(k,b),d(imageWidth,imageHeight)}return{width:Math.floor(imageWidth),height:Math.floor(imageHeight),containerHeight:Math.floor(b),containerWidth:Math.floor(k)+2*settings.horizontal_padding,contentHeight:Math.floor(y),contentWidth:Math.floor(w),resized:resized}}function d(t,i){t=parseFloat(t),i=parseFloat(i),$pp_details=$pp_pic_holder.find(".pp_details"),$pp_details.width(t),detailsHeight=parseFloat($pp_details.css("marginTop"))+parseFloat($pp_details.css("marginBottom")),$pp_details=$pp_details.clone().addClass(settings.theme).width(t).appendTo(e("body")).css({position:"absolute",top:-1e4}),detailsHeight+=$pp_details.height(),detailsHeight=detailsHeight<=34?36:detailsHeight,$pp_details.remove(),$pp_title=$pp_pic_holder.find(".ppt"),$pp_title.width(t),titleHeight=parseFloat($pp_title.css("marginTop"))+parseFloat($pp_title.css("marginBottom")),$pp_title=$pp_title.clone().appendTo(e("body")).css({position:"absolute",top:-1e4}),titleHeight+=$pp_title.height(),$pp_title.remove(),y=i+detailsHeight,w=t,b=y+titleHeight+$pp_pic_holder.find(".pp_top").height()+$pp_pic_holder.find(".pp_bottom").height(),k=t}function h(e){return e.match(/youtube\.com\/watch/i)||e.match(/youtu\.be/i)?"youtube":e.match(/vimeo\.com/i)?"vimeo":e.match(/\b.mov\b/i)?"quicktime":e.match(/\b.swf\b/i)?"flash":e.match(/\biframe=true\b/i)?"iframe":e.match(/\bajax=true\b/i)?"ajax":e.match(/\bcustom=true\b/i)?"custom":"#"==e.substr(0,1)?"inline":"image"}function c(){if(doresize&&"undefined"!=typeof $pp_pic_holder){if(scroll_pos=_(),contentHeight=$pp_pic_holder.height(),contentwidth=$pp_pic_holder.width(),projectedTop=I/2+scroll_pos.scrollTop-contentHeight/2,projectedTop<0&&(projectedTop=0),contentHeight>I)return;$pp_pic_holder.css({top:projectedTop,left:j/2+scroll_pos.scrollLeft-contentwidth/2})}}function _(){return self.pageYOffset?{scrollTop:self.pageYOffset,scrollLeft:self.pageXOffset}:document.documentElement&&document.documentElement.scrollTop?{scrollTop:document.documentElement.scrollTop,scrollLeft:document.documentElement.scrollLeft}:document.body?{scrollTop:document.body.scrollTop,scrollLeft:document.body.scrollLeft}:void 0}function g(){I=e(window).height(),j=e(window).width(),"undefined"!=typeof $pp_overlay&&$pp_overlay.height(e(document).height()).width(j)}function m(){isSet&&settings.overlay_gallery&&"image"==h(pp_images[set_position])?(itemWidth=57,navWidth="facebook"==settings.theme||"pp_default"==settings.theme?50:30,itemsPerPage=Math.floor((f.containerWidth-100-navWidth)/itemWidth),itemsPerPage=itemsPerPage";toInject=settings.gallery_markup.replace(/{gallery}/g,toInject),$pp_pic_holder.find("#pp_full_res").after(toInject),$pp_gallery=e(".pp_pic_holder .pp_gallery"),$pp_gallery_li=$pp_gallery.find("li"),$pp_gallery.find(".pp_arrow_next").click(function(){return e.prettyPhoto.changeGalleryPage("next"),e.prettyPhoto.stopSlideshow(),!1}),$pp_gallery.find(".pp_arrow_previous").click(function(){return e.prettyPhoto.changeGalleryPage("previous"),e.prettyPhoto.stopSlideshow(),!1}),$pp_pic_holder.find(".pp_content").hover(function(){$pp_pic_holder.find(".pp_gallery:not(.disabled)").fadeIn()},function(){$pp_pic_holder.find(".pp_gallery:not(.disabled)").fadeOut()}),itemWidth=57,$pp_gallery_li.each(function(t){e(this).find("a").click(function(){return e.prettyPhoto.changePage(t),e.prettyPhoto.stopSlideshow(),!1})})}settings.slideshow&&($pp_pic_holder.find(".pp_nav").prepend('Play'),$pp_pic_holder.find(".pp_nav .pp_play").click(function(){return e.prettyPhoto.startSlideshow(),!1})),$pp_pic_holder.attr("class","pp_pic_holder "+settings.theme),$pp_overlay.css({opacity:0,height:e(document).height(),width:e(window).width()}).bind("click",function(){settings.modal||e.prettyPhoto.close()}),e("a.pp_close").bind("click",function(){return e.prettyPhoto.close(),!1}),settings.allow_expand&&e("a.pp_expand").bind("click",function(){return e(this).hasClass("pp_expand")?(e(this).removeClass("pp_expand").addClass("pp_contract"),doresize=!1):(e(this).removeClass("pp_contract").addClass("pp_expand"),doresize=!0),n(function(){e.prettyPhoto.open()}),!1}),$pp_pic_holder.find(".pp_previous, .pp_nav .pp_arrow_previous").bind("click",function(){return e.prettyPhoto.changePage("previous"),e.prettyPhoto.stopSlideshow(),!1}),$pp_pic_holder.find(".pp_next, .pp_nav .pp_arrow_next").bind("click",function(){return e.prettyPhoto.changePage("next"),e.prettyPhoto.stopSlideshow(),!1}),c()}a=jQuery.extend({hook:"rel",animation_speed:"fast",ajaxcallback:function(){},slideshow:5e3,autoplay_slideshow:!1,opacity:.8,show_title:!0,allow_resize:!0,allow_expand:!0,default_width:500,default_height:344,counter_separator_label:"/",theme:"pp_default",horizontal_padding:20,hideflash:!1,wmode:"opaque",autoplay:!0,modal:!1,deeplinking:!0,overlay_gallery:!0,overlay_gallery_max:30,keyboard_shortcuts:!0,changepicturecallback:function(){},callback:function(){},ie6_fallback:!0,markup:'
 
',gallery_markup:'',image_markup:'',flash_markup:'',quicktime_markup:'',iframe_markup:'',inline_markup:'
{content}
',custom_markup:"",social_tools:'
'},a);var f,v,y,w,b,k,P,x=this,$=!1,I=e(window).height(),j=e(window).width();return doresize=!0,scroll_pos=_(),e(window).unbind("resize.prettyphoto").bind("resize.prettyphoto",function(){c(),g()}),a.keyboard_shortcuts&&e(document).unbind("keydown.prettyphoto").bind("keydown.prettyphoto",function(t){if("undefined"!=typeof $pp_pic_holder&&$pp_pic_holder.is(":visible"))switch(t.keyCode){case 37:e.prettyPhoto.changePage("previous"),t.preventDefault();break;case 39:e.prettyPhoto.changePage("next"),t.preventDefault();break;case 27:settings.modal||e.prettyPhoto.close(),t.preventDefault()}}),e.prettyPhoto.initialize=function(){return settings=a,"pp_default"==settings.theme&&(settings.horizontal_padding=16),theRel=e(this).attr(settings.hook),galleryRegExp=/\[(?:.*)\]/,isSet=galleryRegExp.exec(theRel)?!0:!1,pp_images=isSet?jQuery.map(x,function(t){return-1!=e(t).attr(settings.hook).indexOf(theRel)?e(t).attr("href"):void 0}):e.makeArray(e(this).attr("href")),pp_titles=isSet?jQuery.map(x,function(t){return-1!=e(t).attr(settings.hook).indexOf(theRel)?e(t).find("img").attr("alt")?e(t).find("img").attr("alt"):"":void 0}):e.makeArray(e(this).find("img").attr("alt")),pp_descriptions=isSet?jQuery.map(x,function(t){return-1!=e(t).attr(settings.hook).indexOf(theRel)?e(t).attr("title")?e(t).attr("title"):"":void 0}):e.makeArray(e(this).attr("title")),pp_images.length>settings.overlay_gallery_max&&(settings.overlay_gallery=!1),set_position=jQuery.inArray(e(this).attr("href"),pp_images),rel_index=isSet?set_position:e("a["+settings.hook+"^='"+theRel+"']").index(e(this)),u(this),settings.allow_resize&&e(window).bind("scroll.prettyphoto",function(){c()}),e.prettyPhoto.open(),!1},e.prettyPhoto.open=function(t){return"undefined"==typeof settings&&(settings=a,pp_images=e.makeArray(arguments[0]),pp_titles=e.makeArray(arguments[1]?arguments[1]:""),pp_descriptions=e.makeArray(arguments[2]?arguments[2]:""),isSet=pp_images.length>1?!0:!1,set_position=arguments[3]?arguments[3]:0,u(t.target)),settings.hideflash&&e("object,embed,iframe[src*=youtube],iframe[src*=vimeo]").css("visibility","hidden"),r(e(pp_images).size()),e(".pp_loaderIcon").show(),settings.deeplinking&&i(),settings.social_tools&&(facebook_like_link=settings.social_tools.replace("{location_href}",encodeURIComponent(location.href)),$pp_pic_holder.find(".pp_social").html(facebook_like_link)),$ppt.is(":hidden")&&$ppt.css("opacity",0).show(),$pp_overlay.show().fadeTo(settings.animation_speed,settings.opacity),$pp_pic_holder.find(".currentTextHolder").text(set_position+1+settings.counter_separator_label+e(pp_images).size()),"undefined"!=typeof pp_descriptions[set_position]&&""!=pp_descriptions[set_position]?$pp_pic_holder.find(".pp_description").show().html(unescape(pp_descriptions[set_position])):$pp_pic_holder.find(".pp_description").hide(),movie_width=parseFloat(o("width",pp_images[set_position]))?o("width",pp_images[set_position]):settings.default_width.toString(),movie_height=parseFloat(o("height",pp_images[set_position]))?o("height",pp_images[set_position]):settings.default_height.toString(),$=!1,-1!=movie_height.indexOf("%")&&(movie_height=parseFloat(e(window).height()*parseFloat(movie_height)/100-150),$=!0),-1!=movie_width.indexOf("%")&&(movie_width=parseFloat(e(window).width()*parseFloat(movie_width)/100-150),$=!0),$pp_pic_holder.fadeIn(function(){switch($ppt.html(settings.show_title&&""!=pp_titles[set_position]&&"undefined"!=typeof pp_titles[set_position]?unescape(pp_titles[set_position]):" "),imgPreloader="",skipInjection=!1,h(pp_images[set_position])){case"image":imgPreloader=new Image,nextImage=new Image,isSet&&set_position0&&(movie_id=movie_id.substr(0,movie_id.indexOf("?"))),movie_id.indexOf("&")>0&&(movie_id=movie_id.substr(0,movie_id.indexOf("&")))),movie="http://www.youtube.com/embed/"+movie_id,movie+=o("rel",pp_images[set_position])?"?rel="+o("rel",pp_images[set_position]):"?rel=1",settings.autoplay&&(movie+="&autoplay=1"),toInject=settings.iframe_markup.replace(/{width}/g,f.width).replace(/{height}/g,f.height).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,movie);break;case"vimeo":f=l(movie_width,movie_height),movie_id=pp_images[set_position];var t=/http(s?):\/\/(www\.)?vimeo.com\/(\d+)/,i=movie_id.match(t);movie="http://player.vimeo.com/video/"+i[3]+"?title=0&byline=0&portrait=0",settings.autoplay&&(movie+="&autoplay=1;"),vimeo_width=f.width+"/embed/?moog_width="+f.width,toInject=settings.iframe_markup.replace(/{width}/g,vimeo_width).replace(/{height}/g,f.height).replace(/{path}/g,movie);break;case"quicktime":f=l(movie_width,movie_height),f.height+=15,f.contentHeight+=15,f.containerHeight+=15,toInject=settings.quicktime_markup.replace(/{width}/g,f.width).replace(/{height}/g,f.height).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,pp_images[set_position]).replace(/{autoplay}/g,settings.autoplay);break;case"flash":f=l(movie_width,movie_height),flash_vars=pp_images[set_position],flash_vars=flash_vars.substring(pp_images[set_position].indexOf("flashvars")+10,pp_images[set_position].length),filename=pp_images[set_position],filename=filename.substring(0,filename.indexOf("?")),toInject=settings.flash_markup.replace(/{width}/g,f.width).replace(/{height}/g,f.height).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,filename+"?"+flash_vars);break;case"iframe":f=l(movie_width,movie_height),frame_url=pp_images[set_position],frame_url=frame_url.substr(0,frame_url.indexOf("iframe")-1),toInject=settings.iframe_markup.replace(/{width}/g,f.width).replace(/{height}/g,f.height).replace(/{path}/g,frame_url);break;case"ajax":doresize=!1,f=l(movie_width,movie_height),doresize=!0,skipInjection=!0,e.get(pp_images[set_position],function(e){toInject=settings.inline_markup.replace(/{content}/g,e),$pp_pic_holder.find("#pp_full_res")[0].innerHTML=toInject,s()});break;case"custom":f=l(movie_width,movie_height),toInject=settings.custom_markup;break;case"inline":myClone=e(pp_images[set_position]).clone().append('
').css({width:settings.default_width}).wrapInner('
').appendTo(e("body")).show(),doresize=!1,f=l(e(myClone).width(),e(myClone).height()),doresize=!0,e(myClone).remove(),toInject=settings.inline_markup.replace(/{content}/g,e(pp_images[set_position]).html())}imgPreloader||skipInjection||($pp_pic_holder.find("#pp_full_res")[0].innerHTML=toInject,s())}),!1},e.prettyPhoto.changePage=function(t){currentGalleryPage=0,"previous"==t?(set_position--,set_position<0&&(set_position=e(pp_images).size()-1)):"next"==t?(set_position++,set_position>e(pp_images).size()-1&&(set_position=0)):set_position=t,rel_index=set_position,doresize||(doresize=!0),settings.allow_expand&&e(".pp_contract").removeClass("pp_contract").addClass("pp_expand"),n(function(){e.prettyPhoto.open()})},e.prettyPhoto.changeGalleryPage=function(e){"next"==e?(currentGalleryPage++,currentGalleryPage>totalPage&&(currentGalleryPage=0)):"previous"==e?(currentGalleryPage--,currentGalleryPage<0&&(currentGalleryPage=totalPage)):currentGalleryPage=e,slide_speed="next"==e||"previous"==e?settings.animation_speed:0,slide_to=currentGalleryPage*itemsPerPage*itemWidth,$pp_gallery.find("ul").animate({left:-slide_to},slide_speed)},e.prettyPhoto.startSlideshow=function(){"undefined"==typeof P?($pp_pic_holder.find(".pp_play").unbind("click").removeClass("pp_play").addClass("pp_pause").click(function(){return e.prettyPhoto.stopSlideshow(),!1}),P=setInterval(e.prettyPhoto.startSlideshow,settings.slideshow)):e.prettyPhoto.changePage("next")},e.prettyPhoto.stopSlideshow=function(){$pp_pic_holder.find(".pp_pause").unbind("click").removeClass("pp_pause").addClass("pp_play").click(function(){return e.prettyPhoto.startSlideshow(),!1}),clearInterval(P),P=void 0},e.prettyPhoto.close=function(){$pp_overlay.is(":animated")||(e.prettyPhoto.stopSlideshow(),$pp_pic_holder.stop().find("object,embed").css("visibility","hidden"),e("div.pp_pic_holder,div.ppt,.pp_fade").fadeOut(settings.animation_speed,function(){e(this).remove()}),$pp_overlay.fadeOut(settings.animation_speed,function(){settings.hideflash&&e("object,embed,iframe[src*=youtube],iframe[src*=vimeo]").css("visibility","visible"),e(this).remove(),e(window).unbind("scroll.prettyphoto"),p(),settings.callback(),doresize=!0,v=!1,delete settings}))},!pp_alreadyInitialized&&t()&&(pp_alreadyInitialized=!0,hashIndex=t(),hashRel=hashIndex,hashIndex=hashIndex.substring(hashIndex.indexOf("/")+1,hashIndex.length-1),hashRel=hashRel.substring(0,hashRel.indexOf("/")),setTimeout(function(){e("a["+a.hook+"^='"+hashRel+"']:eq("+hashIndex+")").trigger("click")},50)),this.unbind("click.prettyphoto").bind("click.prettyphoto",e.prettyPhoto.initialize)}}(jQuery);var pp_alreadyInitialized=!1;; jQuery(document).ready(function(){ jQuery("a[rel^='prettyPhoto']").prettyPhoto(); }); ; (function($) { "use strict"; $(function() { var container = $("#to_top_scrollup").css({ 'opacity': 0 }); var data = to_top_options; var mouse_over = false; var hideEventID = 0; var fnHide = function() { clearTimeout(hideEventID); if (container.is(":visible")) { container.stop().fadeTo(200, 0, function() { container.hide(); mouse_over = false; }); } }; var fnHideEvent = function() { if (!mouse_over && data.enable_autohide == 1 ) { clearTimeout(hideEventID); hideEventID = setTimeout(function() { fnHide(); }, data.autohide_time * 1000); } }; var scrollHandled = false; var fnScroll = function() { if (scrollHandled) return; scrollHandled = true; if ($(window).scrollTop() > data.scroll_offset) { container.stop().css("opacity", mouse_over ? 1 : parseFloat(data.icon_opacity/100)).show(); fnHideEvent(); } else { fnHide(); } scrollHandled = false; }; if ("undefined" != typeof to_top_options.enable_hide_small_device && "1" == to_top_options.enable_hide_small_device) { if ($(window).width() > to_top_options.small_device_max_width) { $(window).scroll(fnScroll); $(document).scroll(fnScroll); } }else{ $(window).scroll(fnScroll); $(document).scroll(fnScroll); } container .hover(function() { clearTimeout(hideEventID); mouse_over = true; $(this).css("opacity", 1); }, function() { $(this).css("opacity", parseFloat(data.icon_opacity/100)); mouse_over = false; fnHideEvent(); }) .click(function() { $("html, body").animate({ scrollTop: 0 }, 400); return false; }); }); })(jQuery);; (function( $ ) { 'use strict'; /** * All of the code for your public-facing JavaScript source * should reside in this file. * * Note: It has been assumed you will write jQuery code here, so the * $ function reference has been prepared for usage within the scope * of this function. * * This enables you to define handlers, for when the DOM is ready: * * $(function() { * * }); * * When the window is loaded: * * $( window ).load(function() { * * }); * * ...and/or other possibilities. * * Ideally, it is not considered best practise to attach more than a * single DOM-ready or window-load handler for a particular page. * Although scripts in the WordPress core, Plugins and Themes may be * practising this, we should strive to set a better example in our own work. */ })( jQuery ); ; /** * Handles the addition of the comment form. * * @since 2.7.0 * @output wp-includes/js/comment-reply.js * * @namespace addComment * * @type {Object} */ window.addComment = ( function( window ) { // Avoid scope lookups on commonly used variables. var document = window.document; // Settings. var config = { commentReplyClass : 'comment-reply-link', cancelReplyId : 'cancel-comment-reply-link', commentFormId : 'commentform', temporaryFormId : 'wp-temp-form-div', parentIdFieldId : 'comment_parent', postIdFieldId : 'comment_post_ID' }; // Cross browser MutationObserver. var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; // Check browser cuts the mustard. var cutsTheMustard = 'querySelector' in document && 'addEventListener' in window; /* * Check browser supports dataset. * !! sets the variable to true if the property exists. */ var supportsDataset = !! document.documentElement.dataset; // For holding the cancel element. var cancelElement; // For holding the comment form element. var commentFormElement; // The respond element. var respondElement; // The mutation observer. var observer; if ( cutsTheMustard && document.readyState !== 'loading' ) { ready(); } else if ( cutsTheMustard ) { window.addEventListener( 'DOMContentLoaded', ready, false ); } /** * Sets up object variables after the DOM is ready. * * @since 5.1.1 */ function ready() { // Initialise the events. init(); // Set up a MutationObserver to check for comments loaded late. observeChanges(); } /** * Add events to links classed .comment-reply-link. * * Searches the context for reply links and adds the JavaScript events * required to move the comment form. To allow for lazy loading of * comments this method is exposed as window.commentReply.init(). * * @since 5.1.0 * * @memberOf addComment * * @param {HTMLElement} context The parent DOM element to search for links. */ function init( context ) { if ( ! cutsTheMustard ) { return; } // Get required elements. cancelElement = getElementById( config.cancelReplyId ); commentFormElement = getElementById( config.commentFormId ); // No cancel element, no replies. if ( ! cancelElement ) { return; } cancelElement.addEventListener( 'touchstart', cancelEvent ); cancelElement.addEventListener( 'click', cancelEvent ); // Submit the comment form when the user types CTRL or CMD + 'Enter'. var submitFormHandler = function( e ) { if ( ( e.metaKey || e.ctrlKey ) && e.keyCode === 13 ) { commentFormElement.removeEventListener( 'keydown', submitFormHandler ); e.preventDefault(); // The submit button ID is 'submit' so we can't call commentFormElement.submit(). Click it instead. commentFormElement.submit.click(); return false; } }; if ( commentFormElement ) { commentFormElement.addEventListener( 'keydown', submitFormHandler ); } var links = replyLinks( context ); var element; for ( var i = 0, l = links.length; i < l; i++ ) { element = links[i]; element.addEventListener( 'touchstart', clickEvent ); element.addEventListener( 'click', clickEvent ); } } /** * Return all links classed .comment-reply-link. * * @since 5.1.0 * * @param {HTMLElement} context The parent DOM element to search for links. * * @return {HTMLCollection|NodeList|Array} */ function replyLinks( context ) { var selectorClass = config.commentReplyClass; var allReplyLinks; // childNodes is a handy check to ensure the context is a HTMLElement. if ( ! context || ! context.childNodes ) { context = document; } if ( document.getElementsByClassName ) { // Fastest. allReplyLinks = context.getElementsByClassName( selectorClass ); } else { // Fast. allReplyLinks = context.querySelectorAll( '.' + selectorClass ); } return allReplyLinks; } /** * Cancel event handler. * * @since 5.1.0 * * @param {Event} event The calling event. */ function cancelEvent( event ) { var cancelLink = this; var temporaryFormId = config.temporaryFormId; var temporaryElement = getElementById( temporaryFormId ); if ( ! temporaryElement || ! respondElement ) { // Conditions for cancel link fail. return; } getElementById( config.parentIdFieldId ).value = '0'; // Move the respond form back in place of the temporary element. temporaryElement.parentNode.replaceChild( respondElement ,temporaryElement ); cancelLink.style.display = 'none'; event.preventDefault(); } /** * Click event handler. * * @since 5.1.0 * * @param {Event} event The calling event. */ function clickEvent( event ) { var replyLink = this, commId = getDataAttribute( replyLink, 'belowelement'), parentId = getDataAttribute( replyLink, 'commentid' ), respondId = getDataAttribute( replyLink, 'respondelement'), postId = getDataAttribute( replyLink, 'postid'), follow; if ( ! commId || ! parentId || ! respondId || ! postId ) { /* * Theme or plugin defines own link via custom `wp_list_comments()` callback * and calls `moveForm()` either directly or via a custom event hook. */ return; } /* * Third party comments systems can hook into this function via the global scope, * therefore the click event needs to reference the global scope. */ follow = window.addComment.moveForm(commId, parentId, respondId, postId); if ( false === follow ) { event.preventDefault(); } } /** * Creates a mutation observer to check for newly inserted comments. * * @since 5.1.0 */ function observeChanges() { if ( ! MutationObserver ) { return; } var observerOptions = { childList: true, subtree: true }; observer = new MutationObserver( handleChanges ); observer.observe( document.body, observerOptions ); } /** * Handles DOM changes, calling init() if any new nodes are added. * * @since 5.1.0 * * @param {Array} mutationRecords Array of MutationRecord objects. */ function handleChanges( mutationRecords ) { var i = mutationRecords.length; while ( i-- ) { // Call init() once if any record in this set adds nodes. if ( mutationRecords[ i ].addedNodes.length ) { init(); return; } } } /** * Backward compatible getter of data-* attribute. * * Uses element.dataset if it exists, otherwise uses getAttribute. * * @since 5.1.0 * * @param {HTMLElement} Element DOM element with the attribute. * @param {String} Attribute the attribute to get. * * @return {String} */ function getDataAttribute( element, attribute ) { if ( supportsDataset ) { return element.dataset[attribute]; } else { return element.getAttribute( 'data-' + attribute ); } } /** * Get element by ID. * * Local alias for document.getElementById. * * @since 5.1.0 * * @param {HTMLElement} The requested element. */ function getElementById( elementId ) { return document.getElementById( elementId ); } /** * Moves the reply form from its current position to the reply location. * * @since 2.7.0 * * @memberOf addComment * * @param {String} addBelowId HTML ID of element the form follows. * @param {String} commentId Database ID of comment being replied to. * @param {String} respondId HTML ID of 'respond' element. * @param {String} postId Database ID of the post. */ function moveForm( addBelowId, commentId, respondId, postId ) { // Get elements based on their IDs. var addBelowElement = getElementById( addBelowId ); respondElement = getElementById( respondId ); // Get the hidden fields. var parentIdField = getElementById( config.parentIdFieldId ); var postIdField = getElementById( config.postIdFieldId ); var element, cssHidden, style; if ( ! addBelowElement || ! respondElement || ! parentIdField ) { // Missing key elements, fail. return; } addPlaceHolder( respondElement ); // Set the value of the post. if ( postId && postIdField ) { postIdField.value = postId; } parentIdField.value = commentId; cancelElement.style.display = ''; addBelowElement.parentNode.insertBefore( respondElement, addBelowElement.nextSibling ); /* * This is for backward compatibility with third party commenting systems * hooking into the event using older techniques. */ cancelElement.onclick = function() { return false; }; // Focus on the first field in the comment form. try { for ( var i = 0; i < commentFormElement.elements.length; i++ ) { element = commentFormElement.elements[i]; cssHidden = false; // Get elements computed style. if ( 'getComputedStyle' in window ) { // Modern browsers. style = window.getComputedStyle( element ); } else if ( document.documentElement.currentStyle ) { // IE 8. style = element.currentStyle; } /* * For display none, do the same thing jQuery does. For visibility, * check the element computed style since browsers are already doing * the job for us. In fact, the visibility computed style is the actual * computed value and already takes into account the element ancestors. */ if ( ( element.offsetWidth <= 0 && element.offsetHeight <= 0 ) || style.visibility === 'hidden' ) { cssHidden = true; } // Skip form elements that are hidden or disabled. if ( 'hidden' === element.type || element.disabled || cssHidden ) { continue; } element.focus(); // Stop after the first focusable element. break; } } catch(e) { } /* * false is returned for backward compatibility with third party commenting systems * hooking into this function. */ return false; } /** * Add placeholder element. * * Places a place holder element above the #respond element for * the form to be returned to if needs be. * * @since 2.7.0 * * @param {HTMLelement} respondElement the #respond element holding comment form. */ function addPlaceHolder( respondElement ) { var temporaryFormId = config.temporaryFormId; var temporaryElement = getElementById( temporaryFormId ); if ( temporaryElement ) { // The element already exists, no need to recreate. return; } temporaryElement = document.createElement( 'div' ); temporaryElement.id = temporaryFormId; temporaryElement.style.display = 'none'; respondElement.parentNode.insertBefore( temporaryElement, respondElement ); } return { init: init, moveForm: moveForm }; })( window ); ; ; /*! * jQuery UI Core 1.11.4 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/category/ui-core/ */ !function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e(jQuery)}(function(a){var e,t,n,i;function r(e,t){var n,i,r,o=e.nodeName.toLowerCase();return"area"===o?(i=(n=e.parentNode).name,!(!e.href||!i||"map"!==n.nodeName.toLowerCase())&&(!!(r=a("img[usemap='#"+i+"']")[0])&&s(r))):(/^(input|select|textarea|button|object)$/.test(o)?!e.disabled:"a"===o&&e.href||t)&&s(e)}function s(e){return a.expr.filters.visible(e)&&!a(e).parents().addBack().filter(function(){return"hidden"===a.css(this,"visibility")}).length}a.ui=a.ui||{},a.extend(a.ui,{version:"1.11.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),a.fn.extend({scrollParent:function(e){var t=this.css("position"),n="absolute"===t,i=e?/(auto|scroll|hidden)/:/(auto|scroll)/,r=this.parents().filter(function(){var e=a(this);return(!n||"static"!==e.css("position"))&&i.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==t&&r.length?r:a(this[0].ownerDocument||document)},uniqueId:(e=0,function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++e)})}),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&a(this).removeAttr("id")})}}),a.extend(a.expr[":"],{data:a.expr.createPseudo?a.expr.createPseudo(function(t){return function(e){return!!a.data(e,t)}}):function(e,t,n){return!!a.data(e,n[3])},focusable:function(e){return r(e,!isNaN(a.attr(e,"tabindex")))},tabbable:function(e){var t=a.attr(e,"tabindex"),n=isNaN(t);return(n||0<=t)&&r(e,!n)}}),a("").outerWidth(1).jquery||a.each(["Width","Height"],function(e,n){var r="Width"===n?["Left","Right"]:["Top","Bottom"],i=n.toLowerCase(),o={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};function s(e,t,n,i){return a.each(r,function(){t-=parseFloat(a.css(e,"padding"+this))||0,n&&(t-=parseFloat(a.css(e,"border"+this+"Width"))||0),i&&(t-=parseFloat(a.css(e,"margin"+this))||0)}),t}a.fn["inner"+n]=function(e){return void 0===e?o["inner"+n].call(this):this.each(function(){a(this).css(i,s(this,e)+"px")})},a.fn["outer"+n]=function(e,t){return"number"!=typeof e?o["outer"+n].call(this,e):this.each(function(){a(this).css(i,s(this,e,!0,t)+"px")})}}),a.fn.addBack||(a.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),a("").data("a-b","a").removeData("a-b").data("a-b")&&(a.fn.removeData=(t=a.fn.removeData,function(e){return arguments.length?t.call(this,a.camelCase(e)):t.call(this)})),a.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),a.fn.extend({focus:(i=a.fn.focus,function(t,n){return"number"==typeof t?this.each(function(){var e=this;setTimeout(function(){a(e).focus(),n&&n.call(e)},t)}):i.apply(this,arguments)}),disableSelection:(n="onselectstart"in document.createElement("div")?"selectstart":"mousedown",function(){return this.bind(n+".ui-disableSelection",function(e){e.preventDefault()})}),enableSelection:function(){return this.unbind(".ui-disableSelection")},zIndex:function(e){if(void 0!==e)return this.css("zIndex",e);if(this.length)for(var t,n,i=a(this[0]);i.length&&i[0]!==document;){if(("absolute"===(t=i.css("position"))||"relative"===t||"fixed"===t)&&(n=parseInt(i.css("zIndex"),10),!isNaN(n)&&0!==n))return n;i=i.parent()}return 0}}),a.ui.plugin={add:function(e,t,n){var i,r=a.ui[e].prototype;for(i in n)r.plugins[i]=r.plugins[i]||[],r.plugins[i].push([t,n[i]])},call:function(e,t,n,i){var r,o=e.plugins[t];if(o&&(i||e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType))for(r=0;r
"),o=e.children()[0];return I("body").append(e),t=o.offsetWidth,e.css("overflow","scroll"),t===(i=o.offsetWidth)&&(i=e[0].clientWidth),e.remove(),n=t-i},getScrollInfo:function(t){var i=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),e=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),o="scroll"===i||"auto"===i&&t.widthx(T(o),T(n))?l.important="horizontal":l.important="vertical",c.using.call(this,t,l)}),f.offset(I.extend(r,{using:t}))})},I.ui.position={fit:{left:function(t,i){var e,o=i.within,n=o.isWindow?o.scrollLeft:o.offset.left,l=o.width,f=t.left-i.collisionPosition.marginLeft,s=n-f,h=f+i.collisionWidth-l-n;i.collisionWidth>l?0l?0' ); $( '.mobmenu-push-wrap' ).after( $( '.mobmenu-left-alignment' ).detach() ); $( '.mobmenu-push-wrap' ).after( $( '.mobmenu-right-alignment' ).detach() ); $( '.mobmenu-push-wrap' ).after( $( '.mob-menu-header-holder' ).detach() ); $( '.mobmenu-push-wrap' ).after( $( '.mobmenu-footer-menu-holder' ).detach() ); $( '.mobmenu-push-wrap' ).after( $( '.mobmenu-overlay' ).detach() ); $( '.mobmenu-push-wrap' ).after( $( '#wpadminbar' ).detach() ); if ( $('.mob-menu-header-holder' ).attr( 'data-detach-el' ) != '' ) { $( '.mobmenu-push-wrap' ).after( $( $('.mob-menu-header-holder' ).attr( 'data-detach-el' ) ).detach() ); } } // Double Check the the menu display classes where added to the body. var menu_display_type = $( '.mob-menu-header-holder' ).attr( 'data-menu-display' ); if ( menu_display_type != '' && !$( 'body' ).hasClass( menu_display_type ) ) { $( 'body' ).addClass( menu_display_type ); } $( 'video' ).each( function(){ if( 'autoplay' === $( this ).attr('autoplay') ) { $( this )[0].play(); } }); var submenu_open_icon = $( '.mob-menu-header-holder' ).attr( 'data-open-icon' ); var submenu_close_icon = $( '.mob-menu-header-holder' ).attr( 'data-close-icon' ); $( '.mobmenu-content .sub-menu' ).each( function(){ $( this ).prev().append('
'); if ( 0 < $( this ).parents( '.mobmenu-parent-link' ).length ) { $( this ).prev().attr('href', '#'); } }); $( document ).on( 'click', '.mobmenu-parent-link .menu-item-has-children' , function ( e ) { if ( e.target.parentElement != this) return; e.preventDefault(); $(this).find('a').find('.mob-expand-submenu').first().trigger('click'); e.stopPropagation(); }); $( document ).on( 'click', '.show-nav-left .mobmenu-push-wrap, .show-nav-left .mobmenu-overlay', function ( e ) { e.preventDefault(); $( '.mobmenu-left-bt' ).first().trigger( 'click' ); e.stopPropagation(); }); $( document ).on( 'click', '.mob-expand-submenu' , function ( e ) { // Check if any menu is open and close it. if ( 1 == $( '.mob-menu-header-holder' ).attr( 'data-autoclose-submenus' ) && ! $(this).parent().next().hasClass( 'show-sub-menu' ) ) { if ( 0 < $( '.mob-expand-submenu.show-sub' ).length && $(this).parents('.show-sub-menu').length <= 0 ) { mobmenuOpenSubmenus( $( '.mob-expand-submenu.show-sub' ) ); } } mobmenuOpenSubmenus( $(this) ); e.preventDefault(); e.stopPropagation(); }); $( document ).on( 'click', '.mobmenu-panel.show-panel .mob-cancel-button, .show-nav-right .mobmenu-overlay, .show-nav-left .mobmenu-overlay', function ( e ) { e.preventDefault(); mobmenuClosePanel( 'show-panel' ); if ( $('body').hasClass('mob-menu-sliding-menus') ) { $( '.mobmenu-trigger-action .hamburger' ).toggleClass('is-active'); } }); $( document ).on( 'click', '.mobmenu-trigger-action', function(e){ e.preventDefault(); var targetPanel = $( this ).attr( 'data-panel-target' ); if ( 'mobmenu-filter-panel' !== targetPanel ) { mobmenuOpenPanel( targetPanel ); } }); $( document ).on( 'click', '.hamburger', function(e){ var targetPanel = $(this).parent().attr('data-panel-target'); e.preventDefault(); e.stopPropagation(); $(this).toggleClass( 'is-active' ); setTimeout(function(){ if ( $( 'body' ).hasClass('show-nav-left') ) { if ( $('body').hasClass('mob-menu-sliding-menus') ) { $( '.mobmenu-trigger-action .hamburger' ).toggleClass('is-active'); } mobmenuClosePanel( targetPanel ); } else { mobmenuOpenPanel( targetPanel ); } }, 400); }); $('.mobmenu a[href*="#"], .mobmenu-panel a[href*="#"]') // Remove links that don't actually link to anything .not('[href="#"]') .not('[href="#0"]') .on( 'click', function(event) { // On-page links if ( location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname && $(this).parents('.mobmenu-content').length > 0 ) { // Figure out element to scroll to var target = $(this.hash); target = target.length ? target : $('[name=' + this.hash.slice(1) + ']'); // Does a scroll target exist? if (target.length) { if ( 0 < $(this).parents('.mobmenu-left-panel').length ) { mobmenuClosePanel( 'mobmenu-left-panel' ); } else { mobmenuClosePanel( 'mobmenu-right-panel' ); } $( 'html' ).css( 'overflow', '' ); $('body,html').animate({ scrollTop: target.offset().top - $(".mob-menu-header-holder").height() - 50 }, 1000); } } }); function mobmenuClosePanel( target ) { $( '.' + target ).toggleClass( 'show-panel' ); $( 'html' ).removeClass( 'show-mobmenu-filter-panel' ); $( 'body' ).removeClass( 'show-nav-right' ); $( 'body' ).removeClass( 'show-nav-left' ); $( 'html' ).removeClass( 'mob-menu-no-scroll' ); } function mobmenuOpenPanel( target) { $( '.mobmenu-content' ).scrollTop(0); $( 'html' ).addClass( 'mob-menu-no-scroll' ); if ( $('.' + target ).hasClass( 'mobmenu-left-alignment' ) ) { $('body').addClass('show-nav-left'); } if ( $('.' + target ).hasClass( 'mobmenu-right-alignment' ) ) { $('body').addClass('show-nav-right'); } $('.' + target ).addClass( 'show-panel' ); } }); ; /*! * hoverIntent v1.8.3 // 2014.08.11 // jQuery v1.9.1+ * http://cherne.net/brian/resources/jquery.hoverIntent.html * * You may use hoverIntent under the terms of the MIT license. Basically that * means you are free to use hoverIntent as long as this header is left intact. * Copyright 2007, 2014 Brian Cherne */ /* hoverIntent is similar to jQuery's built-in "hover" method except that * instead of firing the handlerIn function immediately, hoverIntent checks * to see if the user's mouse has slowed down (beneath the sensitivity * threshold) before firing the event. The handlerOut function is only * called after a matching handlerIn. * * // basic usage ... just like .hover() * .hoverIntent( handlerIn, handlerOut ) * .hoverIntent( handlerInOut ) * * // basic usage ... with event delegation! * .hoverIntent( handlerIn, handlerOut, selector ) * .hoverIntent( handlerInOut, selector ) * * // using a basic configuration object * .hoverIntent( config ) * * @param handlerIn function OR configuration object * @param handlerOut function OR selector for delegation OR undefined * @param selector selector OR undefined * @author Brian Cherne */ (function($) { $.fn.hoverIntent = function(handlerIn,handlerOut,selector) { // default configuration values var cfg = { interval: 100, sensitivity: 6, timeout: 0 }; if ( typeof handlerIn === "object" ) { cfg = $.extend(cfg, handlerIn ); } else if ($.isFunction(handlerOut)) { cfg = $.extend(cfg, { over: handlerIn, out: handlerOut, selector: selector } ); } else { cfg = $.extend(cfg, { over: handlerIn, out: handlerIn, selector: handlerOut } ); } // instantiate variables // cX, cY = current X and Y position of mouse, updated by mousemove event // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval var cX, cY, pX, pY; // A private function for getting mouse position var track = function(ev) { cX = ev.pageX; cY = ev.pageY; }; // A private function for comparing current and previous mouse position var compare = function(ev,ob) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); // compare mouse positions to see if they've crossed the threshold if ( Math.sqrt( (pX-cX)*(pX-cX) + (pY-cY)*(pY-cY) ) < cfg.sensitivity ) { $(ob).off("mousemove.hoverIntent",track); // set hoverIntent state to true (so mouseOut can be called) ob.hoverIntent_s = true; return cfg.over.apply(ob,[ev]); } else { // set previous coordinates for next time pX = cX; pY = cY; // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs) ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval ); } }; // A private function for delaying the mouseOut function var delay = function(ev,ob) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); ob.hoverIntent_s = false; return cfg.out.apply(ob,[ev]); }; // A private function for handling mouse 'hovering' var handleHover = function(e) { // copy objects to be passed into t (required for event object to be passed in IE) var ev = $.extend({},e); var ob = this; // cancel hoverIntent timer if it exists if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); } // if e.type === "mouseenter" if (e.type === "mouseenter") { // set "previous" X and Y position based on initial entry point pX = ev.pageX; pY = ev.pageY; // update "current" X and Y position based on mousemove $(ob).on("mousemove.hoverIntent",track); // start polling interval (self-calling timeout) to compare mouse coordinates over time if (!ob.hoverIntent_s) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );} // else e.type == "mouseleave" } else { // unbind expensive mousemove event $(ob).off("mousemove.hoverIntent",track); // if hoverIntent state is true, then call the mouseOut function after the specified delay if (ob.hoverIntent_s) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );} } }; // listen for mouseenter and mouseleave return this.on({'mouseenter.hoverIntent':handleHover,'mouseleave.hoverIntent':handleHover}, cfg.selector); }; })(jQuery); ; // Underscore.js 1.8.3 // http://underscorejs.org // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. (function() { // Baseline setup // -------------- // Establish the root object, `window` in the browser, or `exports` on the server. var root = this; // Save the previous value of the `_` variable. var previousUnderscore = root._; // Save bytes in the minified (but not gzipped) version: var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. var push = ArrayProto.push, slice = ArrayProto.slice, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use // are declared here. var nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeBind = FuncProto.bind, nativeCreate = Object.create; // Naked function reference for surrogate-prototype-swapping. var Ctor = function(){}; // Create a safe reference to the Underscore object for use below. var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; // Export the Underscore object for **Node.js**, with // backwards-compatibility for the old `require()` API. If we're in // the browser, add `_` as a global object. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } // Current version. _.VERSION = '1.8.3'; // Internal function that returns an efficient (for current engines) version // of the passed-in callback, to be repeatedly applied in other Underscore // functions. var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; switch (argCount == null ? 3 : argCount) { case 1: return function(value) { return func.call(context, value); }; case 2: return function(value, other) { return func.call(context, value, other); }; case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } return function() { return func.apply(context, arguments); }; }; // A mostly-internal function to generate callbacks that can be applied // to each element in a collection, returning the desired result — either // identity, an arbitrary callback, a property matcher, or a property accessor. var cb = function(value, context, argCount) { if (value == null) return _.identity; if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value)) return _.matcher(value); return _.property(value); }; _.iteratee = function(value, context) { return cb(value, context, Infinity); }; // An internal function for creating assigner functions. var createAssigner = function(keysFunc, undefinedOnly) { return function(obj) { var length = arguments.length; if (length < 2 || obj == null) return obj; for (var index = 1; index < length; index++) { var source = arguments[index], keys = keysFunc(source), l = keys.length; for (var i = 0; i < l; i++) { var key = keys[i]; if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; } } return obj; }; }; // An internal function for creating a new object that inherits from another. var baseCreate = function(prototype) { if (!_.isObject(prototype)) return {}; if (nativeCreate) return nativeCreate(prototype); Ctor.prototype = prototype; var result = new Ctor; Ctor.prototype = null; return result; }; var property = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; }; // Helper for collection methods to determine whether a collection // should be iterated as an array or as an object // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; var getLength = property('length'); var isArrayLike = function(collection) { var length = getLength(collection); return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; }; // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `forEach`. // Handles raw objects in addition to array-likes. Treats all // sparse array-likes as if they were dense. _.each = _.forEach = function(obj, iteratee, context) { iteratee = optimizeCb(iteratee, context); var i, length; if (isArrayLike(obj)) { for (i = 0, length = obj.length; i < length; i++) { iteratee(obj[i], i, obj); } } else { var keys = _.keys(obj); for (i = 0, length = keys.length; i < length; i++) { iteratee(obj[keys[i]], keys[i], obj); } } return obj; }; // Return the results of applying the iteratee to each element. _.map = _.collect = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj); } return results; }; // Create a reducing function iterating left or right. function createReduce(dir) { // Optimized iterator function as using arguments.length // in the main function will deoptimize the, see #1991. function iterator(obj, iteratee, memo, keys, index, length) { for (; index >= 0 && index < length; index += dir) { var currentKey = keys ? keys[index] : index; memo = iteratee(memo, obj[currentKey], currentKey, obj); } return memo; } return function(obj, iteratee, memo, context) { iteratee = optimizeCb(iteratee, context, 4); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, index = dir > 0 ? 0 : length - 1; // Determine the initial value if none is provided. if (arguments.length < 3) { memo = obj[keys ? keys[index] : index]; index += dir; } return iterator(obj, iteratee, memo, keys, index, length); }; } // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. _.reduce = _.foldl = _.inject = createReduce(1); // The right-associative version of reduce, also known as `foldr`. _.reduceRight = _.foldr = createReduce(-1); // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { var key; if (isArrayLike(obj)) { key = _.findIndex(obj, predicate, context); } else { key = _.findKey(obj, predicate, context); } if (key !== void 0 && key !== -1) return obj[key]; }; // Return all the elements that pass a truth test. // Aliased as `select`. _.filter = _.select = function(obj, predicate, context) { var results = []; predicate = cb(predicate, context); _.each(obj, function(value, index, list) { if (predicate(value, index, list)) results.push(value); }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { return _.filter(obj, _.negate(cb(predicate)), context); }; // Determine whether all of the elements match a truth test. // Aliased as `all`. _.every = _.all = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (!predicate(obj[currentKey], currentKey, obj)) return false; } return true; }; // Determine if at least one element in the object matches a truth test. // Aliased as `any`. _.some = _.any = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (predicate(obj[currentKey], currentKey, obj)) return true; } return false; }; // Determine if the array or object contains a given item (using `===`). // Aliased as `includes` and `include`. _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { if (!isArrayLike(obj)) obj = _.values(obj); if (typeof fromIndex != 'number' || guard) fromIndex = 0; return _.indexOf(obj, item, fromIndex) >= 0; }; // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); var isFunc = _.isFunction(method); return _.map(obj, function(value) { var func = isFunc ? method : value[method]; return func == null ? func : func.apply(value, args); }); }; // Convenience version of a common use case of `map`: fetching a property. _.pluck = function(obj, key) { return _.map(obj, _.property(key)); }; // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. _.where = function(obj, attrs) { return _.filter(obj, _.matcher(attrs)); }; // Convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. _.findWhere = function(obj, attrs) { return _.find(obj, _.matcher(attrs)); }; // Return the maximum element (or element-based computation). _.max = function(obj, iteratee, context) { var result = -Infinity, lastComputed = -Infinity, value, computed; if (iteratee == null && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value > result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed > lastComputed || computed === -Infinity && result === -Infinity) { result = value; lastComputed = computed; } }); } return result; }; // Return the minimum element (or element-based computation). _.min = function(obj, iteratee, context) { var result = Infinity, lastComputed = Infinity, value, computed; if (iteratee == null && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value < result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed < lastComputed || computed === Infinity && result === Infinity) { result = value; lastComputed = computed; } }); } return result; }; // Shuffle a collection, using the modern version of the // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). _.shuffle = function(obj) { var set = isArrayLike(obj) ? obj : _.values(obj); var length = set.length; var shuffled = Array(length); for (var index = 0, rand; index < length; index++) { rand = _.random(0, index); if (rand !== index) shuffled[index] = shuffled[rand]; shuffled[rand] = set[index]; } return shuffled; }; // Sample **n** random values from a collection. // If **n** is not specified, returns a single random element. // The internal `guard` argument allows it to work with `map`. _.sample = function(obj, n, guard) { if (n == null || guard) { if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } return _.shuffle(obj).slice(0, Math.max(0, n)); }; // Sort the object's values by a criterion produced by an iteratee. _.sortBy = function(obj, iteratee, context) { iteratee = cb(iteratee, context); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, index: index, criteria: iteratee(value, index, list) }; }).sort(function(left, right) { var a = left.criteria; var b = right.criteria; if (a !== b) { if (a > b || a === void 0) return 1; if (a < b || b === void 0) return -1; } return left.index - right.index; }), 'value'); }; // An internal function used for aggregate "group by" operations. var group = function(behavior) { return function(obj, iteratee, context) { var result = {}; iteratee = cb(iteratee, context); _.each(obj, function(value, index) { var key = iteratee(value, index, obj); behavior(result, value, key); }); return result; }; }; // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. _.groupBy = group(function(result, value, key) { if (_.has(result, key)) result[key].push(value); else result[key] = [value]; }); // Indexes the object's values by a criterion, similar to `groupBy`, but for // when you know that your index values will be unique. _.indexBy = group(function(result, value, key) { result[key] = value; }); // Counts instances of an object that group by a certain criterion. Pass // either a string attribute to count by, or a function that returns the // criterion. _.countBy = group(function(result, value, key) { if (_.has(result, key)) result[key]++; else result[key] = 1; }); // Safely create a real, live array from anything iterable. _.toArray = function(obj) { if (!obj) return []; if (_.isArray(obj)) return slice.call(obj); if (isArrayLike(obj)) return _.map(obj, _.identity); return _.values(obj); }; // Return the number of elements in an object. _.size = function(obj) { if (obj == null) return 0; return isArrayLike(obj) ? obj.length : _.keys(obj).length; }; // Split a collection into two arrays: one whose elements all satisfy the given // predicate, and one whose elements all do not satisfy the predicate. _.partition = function(obj, predicate, context) { predicate = cb(predicate, context); var pass = [], fail = []; _.each(obj, function(value, key, obj) { (predicate(value, key, obj) ? pass : fail).push(value); }); return [pass, fail]; }; // Array Functions // --------------- // Get the first element of an array. Passing **n** will return the first N // values in the array. Aliased as `head` and `take`. The **guard** check // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; if (n == null || guard) return array[0]; return _.initial(array, array.length - n); }; // Returns everything but the last entry of the array. Especially useful on // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. _.initial = function(array, n, guard) { return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); }; // Get the last element of an array. Passing **n** will return the last N // values in the array. _.last = function(array, n, guard) { if (array == null) return void 0; if (n == null || guard) return array[array.length - 1]; return _.rest(array, Math.max(0, array.length - n)); }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. // Especially useful on the arguments object. Passing an **n** will return // the rest N values in the array. _.rest = _.tail = _.drop = function(array, n, guard) { return slice.call(array, n == null || guard ? 1 : n); }; // Trim out all falsy values from an array. _.compact = function(array) { return _.filter(array, _.identity); }; // Internal implementation of a recursive `flatten` function. var flatten = function(input, shallow, strict, startIndex) { var output = [], idx = 0; for (var i = startIndex || 0, length = getLength(input); i < length; i++) { var value = input[i]; if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { //flatten current level of array or arguments object if (!shallow) value = flatten(value, shallow, strict); var j = 0, len = value.length; output.length += len; while (j < len) { output[idx++] = value[j++]; } } else if (!strict) { output[idx++] = value; } } return output; }; // Flatten out an array, either recursively (by default), or just one level. _.flatten = function(array, shallow) { return flatten(array, shallow, false); }; // Return a version of the array that does not contain the specified value(s). _.without = function(array) { return _.difference(array, slice.call(arguments, 1)); }; // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. _.uniq = _.unique = function(array, isSorted, iteratee, context) { if (!_.isBoolean(isSorted)) { context = iteratee; iteratee = isSorted; isSorted = false; } if (iteratee != null) iteratee = cb(iteratee, context); var result = []; var seen = []; for (var i = 0, length = getLength(array); i < length; i++) { var value = array[i], computed = iteratee ? iteratee(value, i, array) : value; if (isSorted) { if (!i || seen !== computed) result.push(value); seen = computed; } else if (iteratee) { if (!_.contains(seen, computed)) { seen.push(computed); result.push(value); } } else if (!_.contains(result, value)) { result.push(value); } } return result; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { return _.uniq(flatten(arguments, true, true)); }; // Produce an array that contains every item shared between all the // passed-in arrays. _.intersection = function(array) { var result = []; var argsLength = arguments.length; for (var i = 0, length = getLength(array); i < length; i++) { var item = array[i]; if (_.contains(result, item)) continue; for (var j = 1; j < argsLength; j++) { if (!_.contains(arguments[j], item)) break; } if (j === argsLength) result.push(item); } return result; }; // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { var rest = flatten(arguments, true, true, 1); return _.filter(array, function(value){ return !_.contains(rest, value); }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { return _.unzip(arguments); }; // Complement of _.zip. Unzip accepts an array of arrays and groups // each array's elements on shared indices _.unzip = function(array) { var length = array && _.max(array, getLength).length || 0; var result = Array(length); for (var index = 0; index < length; index++) { result[index] = _.pluck(array, index); } return result; }; // Converts lists into objects. Pass either a single array of `[key, value]` // pairs, or two parallel arrays of the same length -- one of keys, and one of // the corresponding values. _.object = function(list, values) { var result = {}; for (var i = 0, length = getLength(list); i < length; i++) { if (values) { result[list[i]] = values[i]; } else { result[list[i][0]] = list[i][1]; } } return result; }; // Generator function to create the findIndex and findLastIndex functions function createPredicateIndexFinder(dir) { return function(array, predicate, context) { predicate = cb(predicate, context); var length = getLength(array); var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (predicate(array[index], index, array)) return index; } return -1; }; } // Returns the first index on an array-like that passes a predicate test _.findIndex = createPredicateIndexFinder(1); _.findLastIndex = createPredicateIndexFinder(-1); // Use a comparator function to figure out the smallest index at which // an object should be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iteratee, context) { iteratee = cb(iteratee, context, 1); var value = iteratee(obj); var low = 0, high = getLength(array); while (low < high) { var mid = Math.floor((low + high) / 2); if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; } return low; }; // Generator function to create the indexOf and lastIndexOf functions function createIndexFinder(dir, predicateFind, sortedIndex) { return function(array, item, idx) { var i = 0, length = getLength(array); if (typeof idx == 'number') { if (dir > 0) { i = idx >= 0 ? idx : Math.max(idx + length, i); } else { length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; } } else if (sortedIndex && idx && length) { idx = sortedIndex(array, item); return array[idx] === item ? idx : -1; } if (item !== item) { idx = predicateFind(slice.call(array, i, length), _.isNaN); return idx >= 0 ? idx + i : -1; } for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { if (array[idx] === item) return idx; } return -1; }; } // Return the position of the first occurrence of an item in an array, // or -1 if the item is not included in the array. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). _.range = function(start, stop, step) { if (stop == null) { stop = start || 0; start = 0; } step = step || 1; var length = Math.max(Math.ceil((stop - start) / step), 0); var range = Array(length); for (var idx = 0; idx < length; idx++, start += step) { range[idx] = start; } return range; }; // Function (ahem) Functions // ------------------ // Determines whether to execute a function as a constructor // or a normal function with the provided arguments var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; }; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // available. _.bind = function(func, context) { if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); var args = slice.call(arguments, 2); var bound = function() { return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; return bound; }; // Partially apply a function by creating a version that has had some of its // arguments pre-filled, without changing its dynamic `this` context. _ acts // as a placeholder, allowing any combination of arguments to be pre-filled. _.partial = function(func) { var boundArgs = slice.call(arguments, 1); var bound = function() { var position = 0, length = boundArgs.length; var args = Array(length); for (var i = 0; i < length; i++) { args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; } while (position < arguments.length) args.push(arguments[position++]); return executeBound(func, bound, this, this, args); }; return bound; }; // Bind a number of an object's methods to that object. Remaining arguments // are the method names to be bound. Useful for ensuring that all callbacks // defined on an object belong to it. _.bindAll = function(obj) { var i, length = arguments.length, key; if (length <= 1) throw new Error('bindAll must be passed function names'); for (i = 1; i < length; i++) { key = arguments[i]; obj[key] = _.bind(obj[key], obj); } return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { var memoize = function(key) { var cache = memoize.cache; var address = '' + (hasher ? hasher.apply(this, arguments) : key); if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); return cache[address]; }; memoize.cache = {}; return memoize; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); return setTimeout(function(){ return func.apply(null, args); }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. _.defer = _.partial(_.delay, _, 1); // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run // as much as it can, without ever going more than once per `wait` duration; // but if you'd like to disable the execution on the leading edge, pass // `{leading: false}`. To disable execution on the trailing edge, ditto. _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; }; // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. _.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { var last = _.now() - timestamp; if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; }; // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. _.wrap = function(func, wrapper) { return _.partial(wrapper, func); }; // Returns a negated version of the passed-in predicate. _.negate = function(predicate) { return function() { return !predicate.apply(this, arguments); }; }; // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { var args = arguments; var start = args.length - 1; return function() { var i = start; var result = args[start].apply(this, arguments); while (i--) result = args[i].call(this, result); return result; }; }; // Returns a function that will only be executed on and after the Nth call. _.after = function(times, func) { return function() { if (--times < 1) { return func.apply(this, arguments); } }; }; // Returns a function that will only be executed up to (but not including) the Nth call. _.before = function(times, func) { var memo; return function() { if (--times > 0) { memo = func.apply(this, arguments); } if (times <= 1) func = null; return memo; }; }; // Returns a function that will be executed at most one time, no matter how // often you call it. Useful for lazy initialization. _.once = _.partial(_.before, 2); // Object Functions // ---------------- // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; function collectNonEnumProps(obj, keys) { var nonEnumIdx = nonEnumerableProps.length; var constructor = obj.constructor; var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; // Constructor is a special case. var prop = 'constructor'; if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); while (nonEnumIdx--) { prop = nonEnumerableProps[nonEnumIdx]; if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { keys.push(prop); } } } // Retrieve the names of an object's own properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = function(obj) { if (!_.isObject(obj)) return []; if (nativeKeys) return nativeKeys(obj); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); // Ahem, IE < 9. if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; // Retrieve all the property names of an object. _.allKeys = function(obj) { if (!_.isObject(obj)) return []; var keys = []; for (var key in obj) keys.push(key); // Ahem, IE < 9. if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; // Retrieve the values of an object's properties. _.values = function(obj) { var keys = _.keys(obj); var length = keys.length; var values = Array(length); for (var i = 0; i < length; i++) { values[i] = obj[keys[i]]; } return values; }; // Returns the results of applying the iteratee to each element of the object // In contrast to _.map it returns an object _.mapObject = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = _.keys(obj), length = keys.length, results = {}, currentKey; for (var index = 0; index < length; index++) { currentKey = keys[index]; results[currentKey] = iteratee(obj[currentKey], currentKey, obj); } return results; }; // Convert an object into a list of `[key, value]` pairs. _.pairs = function(obj) { var keys = _.keys(obj); var length = keys.length; var pairs = Array(length); for (var i = 0; i < length; i++) { pairs[i] = [keys[i], obj[keys[i]]]; } return pairs; }; // Invert the keys and values of an object. The values must be serializable. _.invert = function(obj) { var result = {}; var keys = _.keys(obj); for (var i = 0, length = keys.length; i < length; i++) { result[obj[keys[i]]] = keys[i]; } return result; }; // Return a sorted list of the function names available on the object. // Aliased as `methods` _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); }; // Extend a given object with all the properties in passed-in object(s). _.extend = createAssigner(_.allKeys); // Assigns a given object with all the own properties in the passed-in object(s) // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) _.extendOwn = _.assign = createAssigner(_.keys); // Returns the first key on an object that passes a predicate test _.findKey = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = _.keys(obj), key; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (predicate(obj[key], key, obj)) return key; } }; // Return a copy of the object only containing the whitelisted properties. _.pick = function(object, oiteratee, context) { var result = {}, obj = object, iteratee, keys; if (obj == null) return result; if (_.isFunction(oiteratee)) { keys = _.allKeys(obj); iteratee = optimizeCb(oiteratee, context); } else { keys = flatten(arguments, false, false, 1); iteratee = function(value, key, obj) { return key in obj; }; obj = Object(obj); } for (var i = 0, length = keys.length; i < length; i++) { var key = keys[i]; var value = obj[key]; if (iteratee(value, key, obj)) result[key] = value; } return result; }; // Return a copy of the object without the blacklisted properties. _.omit = function(obj, iteratee, context) { if (_.isFunction(iteratee)) { iteratee = _.negate(iteratee); } else { var keys = _.map(flatten(arguments, false, false, 1), String); iteratee = function(value, key) { return !_.contains(keys, key); }; } return _.pick(obj, iteratee, context); }; // Fill in a given object with default properties. _.defaults = createAssigner(_.allKeys, true); // Creates an object that inherits from the given prototype object. // If additional properties are provided then they will be added to the // created object. _.create = function(prototype, props) { var result = baseCreate(prototype); if (props) _.extendOwn(result, props); return result; }; // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { if (!_.isObject(obj)) return obj; return _.isArray(obj) ? obj.slice() : _.extend({}, obj); }; // Invokes interceptor with the obj, and then returns obj. // The primary purpose of this method is to "tap into" a method chain, in // order to perform operations on intermediate results within the chain. _.tap = function(obj, interceptor) { interceptor(obj); return obj; }; // Returns whether an object has a given set of `key:value` pairs. _.isMatch = function(object, attrs) { var keys = _.keys(attrs), length = keys.length; if (object == null) return !length; var obj = Object(object); for (var i = 0; i < length; i++) { var key = keys[i]; if (attrs[key] !== obj[key] || !(key in obj)) return false; } return true; }; // Internal recursive comparison function for `isEqual`. var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (a === b) return a !== 0 || 1 / a === 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. if (a instanceof _) a = a._wrapped; if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. var className = toString.call(a); if (className !== toString.call(b)) return false; switch (className) { // Strings, numbers, regular expressions, dates, and booleans are compared by value. case '[object RegExp]': // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return '' + a === '' + b; case '[object Number]': // `NaN`s are equivalent, but non-reflexive. // Object(NaN) is equivalent to NaN if (+a !== +a) return +b !== +b; // An `egal` comparison is performed for other numeric values. return +a === 0 ? 1 / +a === 1 / b : +a === +b; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +a === +b; } var areArrays = className === '[object Array]'; if (!areArrays) { if (typeof a != 'object' || typeof b != 'object') return false; // Objects with different constructors are not equivalent, but `Object`s or `Array`s // from different frames are. var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && _.isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) { return false; } } // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. // Initializing stack of traversed objects. // It's done here since we only need them for objects and arrays comparison. aStack = aStack || []; bStack = bStack || []; var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] === a) return bStack[length] === b; } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); // Recursively compare objects and arrays. if (areArrays) { // Compare array lengths to determine if a deep comparison is necessary. length = a.length; if (length !== b.length) return false; // Deep compare the contents, ignoring non-numeric properties. while (length--) { if (!eq(a[length], b[length], aStack, bStack)) return false; } } else { // Deep compare objects. var keys = _.keys(a), key; length = keys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. if (_.keys(b).length !== length) return false; while (length--) { // Deep compare each member key = keys[length]; if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); return true; }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { return eq(a, b); }; // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (obj == null) return true; if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; return _.keys(obj).length === 0; }; // Is a given value a DOM element? _.isElement = function(obj) { return !!(obj && obj.nodeType === 1); }; // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { return toString.call(obj) === '[object Array]'; }; // Is a given variable an object? _.isObject = function(obj) { var type = typeof obj; return type === 'function' || type === 'object' && !!obj; }; // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { _['is' + name] = function(obj) { return toString.call(obj) === '[object ' + name + ']'; }; }); // Define a fallback version of the method in browsers (ahem, IE < 9), where // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { return _.has(obj, 'callee'); }; } // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, // IE 11 (#1621), and in Safari 8 (#1929). if (typeof /./ != 'function' && typeof Int8Array != 'object') { _.isFunction = function(obj) { return typeof obj == 'function' || false; }; } // Is a given object a finite number? _.isFinite = function(obj) { return isFinite(obj) && !isNaN(parseFloat(obj)); }; // Is the given value `NaN`? (NaN is the only number which does not equal itself). _.isNaN = function(obj) { return _.isNumber(obj) && obj !== +obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; }; // Is a given value equal to null? _.isNull = function(obj) { return obj === null; }; // Is a given variable undefined? _.isUndefined = function(obj) { return obj === void 0; }; // Shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). _.has = function(obj, key) { return obj != null && hasOwnProperty.call(obj, key); }; // Utility Functions // ----------------- // Run Underscore.js in *noConflict* mode, returning the `_` variable to its // previous owner. Returns a reference to the Underscore object. _.noConflict = function() { root._ = previousUnderscore; return this; }; // Keep the identity function around for default iteratees. _.identity = function(value) { return value; }; // Predicate-generating functions. Often useful outside of Underscore. _.constant = function(value) { return function() { return value; }; }; _.noop = function(){}; _.property = property; // Generates a function for a given object that returns a given property. _.propertyOf = function(obj) { return obj == null ? function(){} : function(key) { return obj[key]; }; }; // Returns a predicate for checking whether an object has a given set of // `key:value` pairs. _.matcher = _.matches = function(attrs) { attrs = _.extendOwn({}, attrs); return function(obj) { return _.isMatch(obj, attrs); }; }; // Run a function **n** times. _.times = function(n, iteratee, context) { var accum = Array(Math.max(0, n)); iteratee = optimizeCb(iteratee, context, 1); for (var i = 0; i < n; i++) accum[i] = iteratee(i); return accum; }; // Return a random integer between min and max (inclusive). _.random = function(min, max) { if (max == null) { max = min; min = 0; } return min + Math.floor(Math.random() * (max - min + 1)); }; // A (possibly faster) way to get the current timestamp as an integer. _.now = Date.now || function() { return new Date().getTime(); }; // List of HTML entities for escaping. var escapeMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; var unescapeMap = _.invert(escapeMap); // Functions for escaping and unescaping strings to/from HTML interpolation. var createEscaper = function(map) { var escaper = function(match) { return map[match]; }; // Regexes for identifying a key that needs to be escaped var source = '(?:' + _.keys(map).join('|') + ')'; var testRegexp = RegExp(source); var replaceRegexp = RegExp(source, 'g'); return function(string) { string = string == null ? '' : '' + string; return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; }; }; _.escape = createEscaper(escapeMap); _.unescape = createEscaper(unescapeMap); // If the value of the named `property` is a function then invoke it with the // `object` as context; otherwise, return it. _.result = function(object, property, fallback) { var value = object == null ? void 0 : object[property]; if (value === void 0) { value = fallback; } return _.isFunction(value) ? value.call(object) : value; }; // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; _.uniqueId = function(prefix) { var id = ++idCounter + ''; return prefix ? prefix + id : id; }; // By default, Underscore uses ERB-style template delimiters, change the // following template settings to use alternative delimiters. _.templateSettings = { evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g, escape : /<%-([\s\S]+?)%>/g }; // When customizing `templateSettings`, if you don't want to define an // interpolation, evaluation or escaping regex, we need one that is // guaranteed not to match. var noMatch = /(.)^/; // Certain characters need to be escaped so that they can be put into a // string literal. var escapes = { "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\u2028': 'u2028', '\u2029': 'u2029' }; var escaper = /\\|'|\r|\n|\u2028|\u2029/g; var escapeChar = function(match) { return '\\' + escapes[match]; }; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. // NB: `oldSettings` only exists for backwards compatibility. _.template = function(text, settings, oldSettings) { if (!settings && oldSettings) settings = oldSettings; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. var matcher = RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source ].join('|') + '|$', 'g'); // Compile the template source, escaping string literals appropriately. var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { source += text.slice(index, offset).replace(escaper, escapeChar); index = offset + match.length; if (escape) { source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; } else if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; } else if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } // Adobe VMs need the match returned to produce the correct offest. return match; }); source += "';\n"; // If a variable is not specified, place data values in local scope. if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + source + 'return __p;\n'; try { var render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } var template = function(data) { return render.call(this, data, _); }; // Provide the compiled source as a convenience for precompilation. var argument = settings.variable || 'obj'; template.source = 'function(' + argument + '){\n' + source + '}'; return template; }; // Add a "chain" function. Start chaining a wrapped Underscore object. _.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; }; // OOP // --------------- // If Underscore is called as a function, it returns a wrapped object that // can be used OO-style. This wrapper holds altered versions of all the // underscore functions. Wrapped objects may be chained. // Helper function to continue chaining intermediate results. var result = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; }; // Add your own custom functions to the Underscore object. _.mixin = function(obj) { _.each(_.functions(obj), function(name) { var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return result(this, func.apply(_, args)); }; }); }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; return result(this, obj); }; }); // Add all accessor Array functions to the wrapper. _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result(this, method.apply(this._wrapped, arguments)); }; }); // Extracts the result from a wrapped and chained object. _.prototype.value = function() { return this._wrapped; }; // Provide unwrapping proxy for some methods used in engine operations // such as arithmetic and JSON stringification. _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; _.prototype.toString = function() { return '' + this._wrapped; }; // AMD registration happens at the end for compatibility with AMD loaders // that may not enforce next-turn semantics on modules. Even though general // practice for AMD registration is to be anonymous, underscore registers // as a named module because, like jQuery, it is a base library that is // popular enough to be bundled in a third party lib, but not be part of // an AMD load request. Those cases could generate an error when an // anonymous define() is called outside of a loader request. if (typeof define === 'function' && define.amd) { define('underscore', [], function() { return _; }); } }.call(this)); ; /** * Utility functions for parsing and handling shortcodes in JavaScript. * * @output wp-includes/js/shortcode.js */ /** * Ensure the global `wp` object exists. * * @namespace wp */ window.wp = window.wp || {}; (function(){ wp.shortcode = { // ### Find the next matching shortcode // // Given a shortcode `tag`, a block of `text`, and an optional starting // `index`, returns the next matching shortcode or `undefined`. // // Shortcodes are formatted as an object that contains the match // `content`, the matching `index`, and the parsed `shortcode` object. next: function( tag, text, index ) { var re = wp.shortcode.regexp( tag ), match, result; re.lastIndex = index || 0; match = re.exec( text ); if ( ! match ) { return; } // If we matched an escaped shortcode, try again. if ( '[' === match[1] && ']' === match[7] ) { return wp.shortcode.next( tag, text, re.lastIndex ); } result = { index: match.index, content: match[0], shortcode: wp.shortcode.fromMatch( match ) }; // If we matched a leading `[`, strip it from the match // and increment the index accordingly. if ( match[1] ) { result.content = result.content.slice( 1 ); result.index++; } // If we matched a trailing `]`, strip it from the match. if ( match[7] ) { result.content = result.content.slice( 0, -1 ); } return result; }, // ### Replace matching shortcodes in a block of text // // Accepts a shortcode `tag`, content `text` to scan, and a `callback` // to process the shortcode matches and return a replacement string. // Returns the `text` with all shortcodes replaced. // // Shortcode matches are objects that contain the shortcode `tag`, // a shortcode `attrs` object, the `content` between shortcode tags, // and a boolean flag to indicate if the match was a `single` tag. replace: function( tag, text, callback ) { return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, slash, content, closing, right ) { // If both extra brackets exist, the shortcode has been // properly escaped. if ( left === '[' && right === ']' ) { return match; } // Create the match object and pass it through the callback. var result = callback( wp.shortcode.fromMatch( arguments ) ); // Make sure to return any of the extra brackets if they // weren't used to escape the shortcode. return result ? left + result + right : match; }); }, // ### Generate a string from shortcode parameters // // Creates a `wp.shortcode` instance and returns a string. // // Accepts the same `options` as the `wp.shortcode()` constructor, // containing a `tag` string, a string or object of `attrs`, a boolean // indicating whether to format the shortcode using a `single` tag, and a // `content` string. string: function( options ) { return new wp.shortcode( options ).string(); }, // ### Generate a RegExp to identify a shortcode // // The base regex is functionally equivalent to the one found in // `get_shortcode_regex()` in `wp-includes/shortcodes.php`. // // Capture groups: // // 1. An extra `[` to allow for escaping shortcodes with double `[[]]` // 2. The shortcode name // 3. The shortcode argument list // 4. The self closing `/` // 5. The content of a shortcode when it wraps some content. // 6. The closing tag. // 7. An extra `]` to allow for escaping shortcodes with double `[[]]` regexp: _.memoize( function( tag ) { return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' ); }), // ### Parse shortcode attributes // // Shortcodes accept many types of attributes. These can chiefly be // divided into named and numeric attributes: // // Named attributes are assigned on a key/value basis, while numeric // attributes are treated as an array. // // Named attributes can be formatted as either `name="value"`, // `name='value'`, or `name=value`. Numeric attributes can be formatted // as `"value"` or just `value`. attrs: _.memoize( function( text ) { var named = {}, numeric = [], pattern, match; // This regular expression is reused from `shortcode_parse_atts()` // in `wp-includes/shortcodes.php`. // // Capture groups: // // 1. An attribute name, that corresponds to... // 2. a value in double quotes. // 3. An attribute name, that corresponds to... // 4. a value in single quotes. // 5. An attribute name, that corresponds to... // 6. an unquoted value. // 7. A numeric attribute in double quotes. // 8. A numeric attribute in single quotes. // 9. An unquoted numeric attribute. pattern = /([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*'([^']*)'(?:\s|$)|([\w-]+)\s*=\s*([^\s'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|'([^']*)'(?:\s|$)|(\S+)(?:\s|$)/g; // Map zero-width spaces to actual spaces. text = text.replace( /[\u00a0\u200b]/g, ' ' ); // Match and normalize attributes. while ( (match = pattern.exec( text )) ) { if ( match[1] ) { named[ match[1].toLowerCase() ] = match[2]; } else if ( match[3] ) { named[ match[3].toLowerCase() ] = match[4]; } else if ( match[5] ) { named[ match[5].toLowerCase() ] = match[6]; } else if ( match[7] ) { numeric.push( match[7] ); } else if ( match[8] ) { numeric.push( match[8] ); } else if ( match[9] ) { numeric.push( match[9] ); } } return { named: named, numeric: numeric }; }), // ### Generate a Shortcode Object from a RegExp match // Accepts a `match` object from calling `regexp.exec()` on a `RegExp` // generated by `wp.shortcode.regexp()`. `match` can also be set to the // `arguments` from a callback passed to `regexp.replace()`. fromMatch: function( match ) { var type; if ( match[4] ) { type = 'self-closing'; } else if ( match[6] ) { type = 'closed'; } else { type = 'single'; } return new wp.shortcode({ tag: match[2], attrs: match[3], type: type, content: match[5] }); } }; // Shortcode Objects // ----------------- // // Shortcode objects are generated automatically when using the main // `wp.shortcode` methods: `next()`, `replace()`, and `string()`. // // To access a raw representation of a shortcode, pass an `options` object, // containing a `tag` string, a string or object of `attrs`, a string // indicating the `type` of the shortcode ('single', 'self-closing', or // 'closed'), and a `content` string. wp.shortcode = _.extend( function( options ) { _.extend( this, _.pick( options || {}, 'tag', 'attrs', 'type', 'content' ) ); var attrs = this.attrs; // Ensure we have a correctly formatted `attrs` object. this.attrs = { named: {}, numeric: [] }; if ( ! attrs ) { return; } // Parse a string of attributes. if ( _.isString( attrs ) ) { this.attrs = wp.shortcode.attrs( attrs ); // Identify a correctly formatted `attrs` object. } else if ( _.isEqual( _.keys( attrs ), [ 'named', 'numeric' ] ) ) { this.attrs = attrs; // Handle a flat object of attributes. } else { _.each( options.attrs, function( value, key ) { this.set( key, value ); }, this ); } }, wp.shortcode ); _.extend( wp.shortcode.prototype, { // ### Get a shortcode attribute // // Automatically detects whether `attr` is named or numeric and routes // it accordingly. get: function( attr ) { return this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ]; }, // ### Set a shortcode attribute // // Automatically detects whether `attr` is named or numeric and routes // it accordingly. set: function( attr, value ) { this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ] = value; return this; }, // ### Transform the shortcode match into a string string: function() { var text = '[' + this.tag; _.each( this.attrs.numeric, function( value ) { if ( /\s/.test( value ) ) { text += ' "' + value + '"'; } else { text += ' ' + value; } }); _.each( this.attrs.named, function( value, name ) { text += ' ' + name + '="' + value + '"'; }); // If the tag is marked as `single` or `self-closing`, close the // tag and ignore any additional content. if ( 'single' === this.type ) { return text + ']'; } else if ( 'self-closing' === this.type ) { return text + ' /]'; } // Complete the opening tag. text += ']'; if ( this.content ) { text += this.content; } // Add the closing tag. return text + '[/' + this.tag + ']'; } }); }()); // HTML utility functions // ---------------------- // // Experimental. These functions may change or be removed in the future. (function(){ wp.html = _.extend( wp.html || {}, { // ### Parse HTML attributes. // // Converts `content` to a set of parsed HTML attributes. // Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of // the HTML attribute specification. Reformats the attributes into an // object that contains the `attrs` with `key:value` mapping, and a record // of the attributes that were entered using `empty` attribute syntax (i.e. // with no value). attrs: function( content ) { var result, attrs; // If `content` ends in a slash, strip it. if ( '/' === content[ content.length - 1 ] ) { content = content.slice( 0, -1 ); } result = wp.shortcode.attrs( content ); attrs = result.named; _.each( result.numeric, function( key ) { if ( /\s/.test( key ) ) { return; } attrs[ key ] = ''; }); return attrs; }, // ### Convert an HTML-representation of an object to a string. string: function( options ) { var text = '<' + options.tag, content = options.content || ''; _.each( options.attrs, function( value, attr ) { text += ' ' + attr; // Convert boolean values to strings. if ( _.isBoolean( value ) ) { value = value ? 'true' : 'false'; } text += '="' + value + '"'; }); // Return the result if it is a self-closing tag. if ( options.single ) { return text + ' />'; } // Complete the opening tag. text += '>'; // If `content` is an object, recursively call this function. text += _.isObject( content ) ? wp.html.string( content ) : content; return text + ''; } }); }()); ; /** * Cookie functions. * * @output wp-includes/js/utils.js */ /* global userSettings, getAllUserSettings, wpCookies, setUserSetting */ /* exported getUserSetting, setUserSetting, deleteUserSetting */ window.wpCookies = { // The following functions are from Cookie.js class in TinyMCE 3, Moxiecode, used under LGPL. each: function( obj, cb, scope ) { var n, l; if ( ! obj ) { return 0; } scope = scope || obj; if ( typeof( obj.length ) !== 'undefined' ) { for ( n = 0, l = obj.length; n < l; n++ ) { if ( cb.call( scope, obj[n], n, obj ) === false ) { return 0; } } } else { for ( n in obj ) { if ( obj.hasOwnProperty(n) ) { if ( cb.call( scope, obj[n], n, obj ) === false ) { return 0; } } } } return 1; }, /** * Get a multi-values cookie. * Returns a JS object with the name: 'value' pairs. */ getHash: function( name ) { var cookie = this.get( name ), values; if ( cookie ) { this.each( cookie.split('&'), function( pair ) { pair = pair.split('='); values = values || {}; values[pair[0]] = pair[1]; }); } return values; }, /** * Set a multi-values cookie. * * 'values_obj' is the JS object that is stored. It is encoded as URI in wpCookies.set(). */ setHash: function( name, values_obj, expires, path, domain, secure ) { var str = ''; this.each( values_obj, function( val, key ) { str += ( ! str ? '' : '&' ) + key + '=' + val; }); this.set( name, str, expires, path, domain, secure ); }, /** * Get a cookie. */ get: function( name ) { var e, b, cookie = document.cookie, p = name + '='; if ( ! cookie ) { return; } b = cookie.indexOf( '; ' + p ); if ( b === -1 ) { b = cookie.indexOf(p); if ( b !== 0 ) { return null; } } else { b += 2; } e = cookie.indexOf( ';', b ); if ( e === -1 ) { e = cookie.length; } return decodeURIComponent( cookie.substring( b + p.length, e ) ); }, /** * Set a cookie. * * The 'expires' arg can be either a JS Date() object set to the expiration date (back-compat) * or the number of seconds until expiration */ set: function( name, value, expires, path, domain, secure ) { var d = new Date(); if ( typeof( expires ) === 'object' && expires.toGMTString ) { expires = expires.toGMTString(); } else if ( parseInt( expires, 10 ) ) { d.setTime( d.getTime() + ( parseInt( expires, 10 ) * 1000 ) ); // time must be in milliseconds expires = d.toGMTString(); } else { expires = ''; } document.cookie = name + '=' + encodeURIComponent( value ) + ( expires ? '; expires=' + expires : '' ) + ( path ? '; path=' + path : '' ) + ( domain ? '; domain=' + domain : '' ) + ( secure ? '; secure' : '' ); }, /** * Remove a cookie. * * This is done by setting it to an empty value and setting the expiration time in the past. */ remove: function( name, path, domain, secure ) { this.set( name, '', -1000, path, domain, secure ); } }; // Returns the value as string. Second arg or empty string is returned when value is not set. window.getUserSetting = function( name, def ) { var settings = getAllUserSettings(); if ( settings.hasOwnProperty( name ) ) { return settings[name]; } if ( typeof def !== 'undefined' ) { return def; } return ''; }; // Both name and value must be only ASCII letters, numbers or underscore // and the shorter, the better (cookies can store maximum 4KB). Not suitable to store text. // The value is converted and stored as string. window.setUserSetting = function( name, value, _del ) { if ( 'object' !== typeof userSettings ) { return false; } var uid = userSettings.uid, settings = wpCookies.getHash( 'wp-settings-' + uid ), path = userSettings.url, secure = !! userSettings.secure; name = name.toString().replace( /[^A-Za-z0-9_-]/g, '' ); if ( typeof value === 'number' ) { value = parseInt( value, 10 ); } else { value = value.toString().replace( /[^A-Za-z0-9_-]/g, '' ); } settings = settings || {}; if ( _del ) { delete settings[name]; } else { settings[name] = value; } wpCookies.setHash( 'wp-settings-' + uid, settings, 31536000, path, '', secure ); wpCookies.set( 'wp-settings-time-' + uid, userSettings.time, 31536000, path, '', secure ); return name; }; window.deleteUserSetting = function( name ) { return setUserSetting( name, '', 1 ); }; // Returns all settings as js object. window.getAllUserSettings = function() { if ( 'object' !== typeof userSettings ) { return {}; } return wpCookies.getHash( 'wp-settings-' + userSettings.uid ) || {}; }; ; // Backbone.js 1.4.0 // (c) 2010-2019 Jeremy Ashkenas and DocumentCloud // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org (function(factory) { // Establish the root object, `window` (`self`) in the browser, or `global` on the server. // We use `self` instead of `window` for `WebWorker` support. var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global; // Set up Backbone appropriately for the environment. Start with AMD. if (typeof define === 'function' && define.amd) { define(['underscore', 'jquery', 'exports'], function(_, $, exports) { // Export global even in AMD case in case this script is loaded with // others that may still expect a global Backbone. root.Backbone = factory(root, exports, _, $); }); // Next for Node.js or CommonJS. jQuery may not be needed as a module. } else if (typeof exports !== 'undefined') { var _ = require('underscore'), $; try { $ = require('jquery'); } catch (e) {} factory(root, exports, _, $); // Finally, as a browser global. } else { root.Backbone = factory(root, {}, root._, root.jQuery || root.Zepto || root.ender || root.$); } })(function(root, Backbone, _, $) { // Initial Setup // ------------- // Save the previous value of the `Backbone` variable, so that it can be // restored later on, if `noConflict` is used. var previousBackbone = root.Backbone; // Create a local reference to a common array method we'll want to use later. var slice = Array.prototype.slice; // Current version of the library. Keep in sync with `package.json`. Backbone.VERSION = '1.4.0'; // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns // the `$` variable. Backbone.$ = $; // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable // to its previous owner. Returns a reference to this Backbone object. Backbone.noConflict = function() { root.Backbone = previousBackbone; return this; }; // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and // set a `X-Http-Method-Override` header. Backbone.emulateHTTP = false; // Turn on `emulateJSON` to support legacy servers that can't deal with direct // `application/json` requests ... this will encode the body as // `application/x-www-form-urlencoded` instead and will send the model in a // form param named `model`. Backbone.emulateJSON = false; // Backbone.Events // --------------- // A module that can be mixed in to *any object* in order to provide it with // a custom event channel. You may bind a callback to an event with `on` or // remove with `off`; `trigger`-ing an event fires all callbacks in // succession. // // var object = {}; // _.extend(object, Backbone.Events); // object.on('expand', function(){ alert('expanded'); }); // object.trigger('expand'); // var Events = Backbone.Events = {}; // Regular expression used to split event strings. var eventSplitter = /\s+/; // A private global variable to share between listeners and listenees. var _listening; // Iterates over the standard `event, callback` (as well as the fancy multiple // space-separated events `"change blur", callback` and jQuery-style event // maps `{event: callback}`). var eventsApi = function(iteratee, events, name, callback, opts) { var i = 0, names; if (name && typeof name === 'object') { // Handle event maps. if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback; for (names = _.keys(name); i < names.length ; i++) { events = eventsApi(iteratee, events, names[i], name[names[i]], opts); } } else if (name && eventSplitter.test(name)) { // Handle space-separated event names by delegating them individually. for (names = name.split(eventSplitter); i < names.length; i++) { events = iteratee(events, names[i], callback, opts); } } else { // Finally, standard events. events = iteratee(events, name, callback, opts); } return events; }; // Bind an event to a `callback` function. Passing `"all"` will bind // the callback to all events fired. Events.on = function(name, callback, context) { this._events = eventsApi(onApi, this._events || {}, name, callback, { context: context, ctx: this, listening: _listening }); if (_listening) { var listeners = this._listeners || (this._listeners = {}); listeners[_listening.id] = _listening; // Allow the listening to use a counter, instead of tracking // callbacks for library interop _listening.interop = false; } return this; }; // Inversion-of-control versions of `on`. Tell *this* object to listen to // an event in another object... keeping track of what it's listening to // for easier unbinding later. Events.listenTo = function(obj, name, callback) { if (!obj) return this; var id = obj._listenId || (obj._listenId = _.uniqueId('l')); var listeningTo = this._listeningTo || (this._listeningTo = {}); var listening = _listening = listeningTo[id]; // This object is not listening to any other events on `obj` yet. // Setup the necessary references to track the listening callbacks. if (!listening) { this._listenId || (this._listenId = _.uniqueId('l')); listening = _listening = listeningTo[id] = new Listening(this, obj); } // Bind callbacks on obj. var error = tryCatchOn(obj, name, callback, this); _listening = void 0; if (error) throw error; // If the target obj is not Backbone.Events, track events manually. if (listening.interop) listening.on(name, callback); return this; }; // The reducing API that adds a callback to the `events` object. var onApi = function(events, name, callback, options) { if (callback) { var handlers = events[name] || (events[name] = []); var context = options.context, ctx = options.ctx, listening = options.listening; if (listening) listening.count++; handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening}); } return events; }; // An try-catch guarded #on function, to prevent poisoning the global // `_listening` variable. var tryCatchOn = function(obj, name, callback, context) { try { obj.on(name, callback, context); } catch (e) { return e; } }; // Remove one or many callbacks. If `context` is null, removes all // callbacks with that function. If `callback` is null, removes all // callbacks for the event. If `name` is null, removes all bound // callbacks for all events. Events.off = function(name, callback, context) { if (!this._events) return this; this._events = eventsApi(offApi, this._events, name, callback, { context: context, listeners: this._listeners }); return this; }; // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. Events.stopListening = function(obj, name, callback) { var listeningTo = this._listeningTo; if (!listeningTo) return this; var ids = obj ? [obj._listenId] : _.keys(listeningTo); for (var i = 0; i < ids.length; i++) { var listening = listeningTo[ids[i]]; // If listening doesn't exist, this object is not currently // listening to obj. Break out early. if (!listening) break; listening.obj.off(name, callback, this); if (listening.interop) listening.off(name, callback); } if (_.isEmpty(listeningTo)) this._listeningTo = void 0; return this; }; // The reducing API that removes a callback from the `events` object. var offApi = function(events, name, callback, options) { if (!events) return; var context = options.context, listeners = options.listeners; var i = 0, names; // Delete all event listeners and "drop" events. if (!name && !context && !callback) { for (names = _.keys(listeners); i < names.length; i++) { listeners[names[i]].cleanup(); } return; } names = name ? [name] : _.keys(events); for (; i < names.length; i++) { name = names[i]; var handlers = events[name]; // Bail out if there are no events stored. if (!handlers) break; // Find any remaining events. var remaining = []; for (var j = 0; j < handlers.length; j++) { var handler = handlers[j]; if ( callback && callback !== handler.callback && callback !== handler.callback._callback || context && context !== handler.context ) { remaining.push(handler); } else { var listening = handler.listening; if (listening) listening.off(name, callback); } } // Replace events if there are any remaining. Otherwise, clean up. if (remaining.length) { events[name] = remaining; } else { delete events[name]; } } return events; }; // Bind an event to only be triggered a single time. After the first time // the callback is invoked, its listener will be removed. If multiple events // are passed in using the space-separated syntax, the handler will fire // once for each event, not once for a combination of all events. Events.once = function(name, callback, context) { // Map the event into a `{event: once}` object. var events = eventsApi(onceMap, {}, name, callback, this.off.bind(this)); if (typeof name === 'string' && context == null) callback = void 0; return this.on(events, callback, context); }; // Inversion-of-control versions of `once`. Events.listenToOnce = function(obj, name, callback) { // Map the event into a `{event: once}` object. var events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj)); return this.listenTo(obj, events); }; // Reduces the event callbacks into a map of `{event: onceWrapper}`. // `offer` unbinds the `onceWrapper` after it has been called. var onceMap = function(map, name, callback, offer) { if (callback) { var once = map[name] = _.once(function() { offer(name, once); callback.apply(this, arguments); }); once._callback = callback; } return map; }; // Trigger one or many events, firing all bound callbacks. Callbacks are // passed the same arguments as `trigger` is, apart from the event name // (unless you're listening on `"all"`, which will cause your callback to // receive the true name of the event as the first argument). Events.trigger = function(name) { if (!this._events) return this; var length = Math.max(0, arguments.length - 1); var args = Array(length); for (var i = 0; i < length; i++) args[i] = arguments[i + 1]; eventsApi(triggerApi, this._events, name, void 0, args); return this; }; // Handles triggering the appropriate event callbacks. var triggerApi = function(objEvents, name, callback, args) { if (objEvents) { var events = objEvents[name]; var allEvents = objEvents.all; if (events && allEvents) allEvents = allEvents.slice(); if (events) triggerEvents(events, args); if (allEvents) triggerEvents(allEvents, [name].concat(args)); } return objEvents; }; // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // Backbone events have 3 arguments). var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; } }; // A listening class that tracks and cleans up memory bindings // when all callbacks have been offed. var Listening = function(listener, obj) { this.id = listener._listenId; this.listener = listener; this.obj = obj; this.interop = true; this.count = 0; this._events = void 0; }; Listening.prototype.on = Events.on; // Offs a callback (or several). // Uses an optimized counter if the listenee uses Backbone.Events. // Otherwise, falls back to manual tracking to support events // library interop. Listening.prototype.off = function(name, callback) { var cleanup; if (this.interop) { this._events = eventsApi(offApi, this._events, name, callback, { context: void 0, listeners: void 0 }); cleanup = !this._events; } else { this.count--; cleanup = this.count === 0; } if (cleanup) this.cleanup(); }; // Cleans up memory bindings between the listener and the listenee. Listening.prototype.cleanup = function() { delete this.listener._listeningTo[this.obj._listenId]; if (!this.interop) delete this.obj._listeners[this.id]; }; // Aliases for backwards compatibility. Events.bind = Events.on; Events.unbind = Events.off; // Allow the `Backbone` object to serve as a global event bus, for folks who // want global "pubsub" in a convenient place. _.extend(Backbone, Events); // Backbone.Model // -------------- // Backbone **Models** are the basic data object in the framework -- // frequently representing a row in a table in a database on your server. // A discrete chunk of data and a bunch of useful, related methods for // performing computations and transformations on that data. // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. var Model = Backbone.Model = function(attributes, options) { var attrs = attributes || {}; options || (options = {}); this.preinitialize.apply(this, arguments); this.cid = _.uniqueId(this.cidPrefix); this.attributes = {}; if (options.collection) this.collection = options.collection; if (options.parse) attrs = this.parse(attrs, options) || {}; var defaults = _.result(this, 'defaults'); attrs = _.defaults(_.extend({}, defaults, attrs), defaults); this.set(attrs, options); this.changed = {}; this.initialize.apply(this, arguments); }; // Attach all inheritable methods to the Model prototype. _.extend(Model.prototype, Events, { // A hash of attributes whose current and previous value differ. changed: null, // The value returned during the last failed validation. validationError: null, // The default name for the JSON `id` attribute is `"id"`. MongoDB and // CouchDB users may want to set this to `"_id"`. idAttribute: 'id', // The prefix is used to create the client id which is used to identify models locally. // You may want to override this if you're experiencing name clashes with model ids. cidPrefix: 'c', // preinitialize is an empty function by default. You can override it with a function // or object. preinitialize will run before any instantiation logic is run in the Model. preinitialize: function(){}, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // Return a copy of the model's `attributes` object. toJSON: function(options) { return _.clone(this.attributes); }, // Proxy `Backbone.sync` by default -- but override this if you need // custom syncing semantics for *this* particular model. sync: function() { return Backbone.sync.apply(this, arguments); }, // Get the value of an attribute. get: function(attr) { return this.attributes[attr]; }, // Get the HTML-escaped value of an attribute. escape: function(attr) { return _.escape(this.get(attr)); }, // Returns `true` if the attribute contains a value that is not null // or undefined. has: function(attr) { return this.get(attr) != null; }, // Special-cased proxy to underscore's `_.matches` method. matches: function(attrs) { return !!_.iteratee(attrs, this)(this.attributes); }, // Set a hash of model attributes on the object, firing `"change"`. This is // the core primitive operation of a model, updating the data and notifying // anyone who needs to know about the change in state. The heart of the beast. set: function(key, val, options) { if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments. var attrs; if (typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options || (options = {}); // Run validation. if (!this._validate(attrs, options)) return false; // Extract attributes and options. var unset = options.unset; var silent = options.silent; var changes = []; var changing = this._changing; this._changing = true; if (!changing) { this._previousAttributes = _.clone(this.attributes); this.changed = {}; } var current = this.attributes; var changed = this.changed; var prev = this._previousAttributes; // For each `set` attribute, update or delete the current value. for (var attr in attrs) { val = attrs[attr]; if (!_.isEqual(current[attr], val)) changes.push(attr); if (!_.isEqual(prev[attr], val)) { changed[attr] = val; } else { delete changed[attr]; } unset ? delete current[attr] : current[attr] = val; } // Update the `id`. if (this.idAttribute in attrs) this.id = this.get(this.idAttribute); // Trigger all relevant attribute changes. if (!silent) { if (changes.length) this._pending = options; for (var i = 0; i < changes.length; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } // You might be wondering why there's a `while` loop here. Changes can // be recursively nested within `"change"` events. if (changing) return this; if (!silent) { while (this._pending) { options = this._pending; this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; }, // Remove an attribute from the model, firing `"change"`. `unset` is a noop // if the attribute doesn't exist. unset: function(attr, options) { return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, // Clear all attributes on the model, firing `"change"`. clear: function(options) { var attrs = {}; for (var key in this.attributes) attrs[key] = void 0; return this.set(attrs, _.extend({}, options, {unset: true})); }, // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. hasChanged: function(attr) { if (attr == null) return !_.isEmpty(this.changed); return _.has(this.changed, attr); }, // Return an object containing all the attributes that have changed, or // false if there are no changed attributes. Useful for determining what // parts of a view need to be updated and/or what attributes need to be // persisted to the server. Unset attributes will be set to undefined. // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. changedAttributes: function(diff) { if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; var old = this._changing ? this._previousAttributes : this.attributes; var changed = {}; var hasChanged; for (var attr in diff) { var val = diff[attr]; if (_.isEqual(old[attr], val)) continue; changed[attr] = val; hasChanged = true; } return hasChanged ? changed : false; }, // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. previous: function(attr) { if (attr == null || !this._previousAttributes) return null; return this._previousAttributes[attr]; }, // Get all of the attributes of the model at the time of the previous // `"change"` event. previousAttributes: function() { return _.clone(this._previousAttributes); }, // Fetch the model from the server, merging the response with the model's // local attributes. Any changed attributes will trigger a "change" event. fetch: function(options) { options = _.extend({parse: true}, options); var model = this; var success = options.success; options.success = function(resp) { var serverAttrs = options.parse ? model.parse(resp, options) : resp; if (!model.set(serverAttrs, options)) return false; if (success) success.call(options.context, model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. save: function(key, val, options) { // Handle both `"key", value` and `{key: value}` -style arguments. var attrs; if (key == null || typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options = _.extend({validate: true, parse: true}, options); var wait = options.wait; // If we're not waiting and attributes exist, save acts as // `set(attr).save(null, opts)` with validation. Otherwise, check if // the model will be valid when the attributes, if any, are set. if (attrs && !wait) { if (!this.set(attrs, options)) return false; } else if (!this._validate(attrs, options)) { return false; } // After a successful server-side save, the client is (optionally) // updated with the server-side state. var model = this; var success = options.success; var attributes = this.attributes; options.success = function(resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes; var serverAttrs = options.parse ? model.parse(resp, options) : resp; if (wait) serverAttrs = _.extend({}, attrs, serverAttrs); if (serverAttrs && !model.set(serverAttrs, options)) return false; if (success) success.call(options.context, model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); // Set temporary attributes if `{wait: true}` to properly find new ids. if (attrs && wait) this.attributes = _.extend({}, attributes, attrs); var method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update'; if (method === 'patch' && !options.attrs) options.attrs = attrs; var xhr = this.sync(method, this, options); // Restore attributes. this.attributes = attributes; return xhr; }, // Destroy this model on the server if it was already persisted. // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. destroy: function(options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; var wait = options.wait; var destroy = function() { model.stopListening(); model.trigger('destroy', model, model.collection, options); }; options.success = function(resp) { if (wait) destroy(); if (success) success.call(options.context, model, resp, options); if (!model.isNew()) model.trigger('sync', model, resp, options); }; var xhr = false; if (this.isNew()) { _.defer(options.success); } else { wrapError(this, options); xhr = this.sync('delete', this, options); } if (!wait) destroy(); return xhr; }, // Default URL for the model's representation on the server -- if you're // using Backbone's restful methods, override this to change the endpoint // that will be called. url: function() { var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); if (this.isNew()) return base; var id = this.get(this.idAttribute); return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id); }, // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. parse: function(resp, options) { return resp; }, // Create a new model with identical attributes to this one. clone: function() { return new this.constructor(this.attributes); }, // A model is new if it has never been saved to the server, and lacks an id. isNew: function() { return !this.has(this.idAttribute); }, // Check if the model is currently in a valid state. isValid: function(options) { return this._validate({}, _.extend({}, options, {validate: true})); }, // Run validation against the next complete set of model attributes, // returning `true` if all is well. Otherwise, fire an `"invalid"` event. _validate: function(attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; if (!error) return true; this.trigger('invalid', this, error, _.extend(options, {validationError: error})); return false; } }); // Backbone.Collection // ------------------- // If models tend to represent a single row of data, a Backbone Collection is // more analogous to a table full of data ... or a small slice or page of that // table, or a collection of rows that belong together for a particular reason // -- all of the messages in this particular folder, all of the documents // belonging to this particular author, and so on. Collections maintain // indexes of their models, both in order, and for lookup by `id`. // Create a new **Collection**, perhaps to contain a specific type of `model`. // If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. var Collection = Backbone.Collection = function(models, options) { options || (options = {}); this.preinitialize.apply(this, arguments); if (options.model) this.model = options.model; if (options.comparator !== void 0) this.comparator = options.comparator; this._reset(); this.initialize.apply(this, arguments); if (models) this.reset(models, _.extend({silent: true}, options)); }; // Default options for `Collection#set`. var setOptions = {add: true, remove: true, merge: true}; var addOptions = {add: true, remove: false}; // Splices `insert` into `array` at index `at`. var splice = function(array, insert, at) { at = Math.min(Math.max(at, 0), array.length); var tail = Array(array.length - at); var length = insert.length; var i; for (i = 0; i < tail.length; i++) tail[i] = array[i + at]; for (i = 0; i < length; i++) array[i + at] = insert[i]; for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i]; }; // Define the Collection's inheritable methods. _.extend(Collection.prototype, Events, { // The default model for a collection is just a **Backbone.Model**. // This should be overridden in most cases. model: Model, // preinitialize is an empty function by default. You can override it with a function // or object. preinitialize will run before any instantiation logic is run in the Collection. preinitialize: function(){}, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // The JSON representation of a Collection is an array of the // models' attributes. toJSON: function(options) { return this.map(function(model) { return model.toJSON(options); }); }, // Proxy `Backbone.sync` by default. sync: function() { return Backbone.sync.apply(this, arguments); }, // Add a model, or list of models to the set. `models` may be Backbone // Models or raw JavaScript objects to be converted to Models, or any // combination of the two. add: function(models, options) { return this.set(models, _.extend({merge: false}, options, addOptions)); }, // Remove a model, or a list of models from the set. remove: function(models, options) { options = _.extend({}, options); var singular = !_.isArray(models); models = singular ? [models] : models.slice(); var removed = this._removeModels(models, options); if (!options.silent && removed.length) { options.changes = {added: [], merged: [], removed: removed}; this.trigger('update', this, options); } return singular ? removed[0] : removed; }, // Update a collection by `set`-ing a new list of models, adding new ones, // removing models that are no longer present, and merging models that // already exist in the collection, as necessary. Similar to **Model#set**, // the core operation for updating the data contained by the collection. set: function(models, options) { if (models == null) return; options = _.extend({}, setOptions, options); if (options.parse && !this._isModel(models)) { models = this.parse(models, options) || []; } var singular = !_.isArray(models); models = singular ? [models] : models.slice(); var at = options.at; if (at != null) at = +at; if (at > this.length) at = this.length; if (at < 0) at += this.length + 1; var set = []; var toAdd = []; var toMerge = []; var toRemove = []; var modelMap = {}; var add = options.add; var merge = options.merge; var remove = options.remove; var sort = false; var sortable = this.comparator && at == null && options.sort !== false; var sortAttr = _.isString(this.comparator) ? this.comparator : null; // Turn bare objects into model references, and prevent invalid models // from being added. var model, i; for (i = 0; i < models.length; i++) { model = models[i]; // If a duplicate is found, prevent it from being added and // optionally merge it into the existing model. var existing = this.get(model); if (existing) { if (merge && model !== existing) { var attrs = this._isModel(model) ? model.attributes : model; if (options.parse) attrs = existing.parse(attrs, options); existing.set(attrs, options); toMerge.push(existing); if (sortable && !sort) sort = existing.hasChanged(sortAttr); } if (!modelMap[existing.cid]) { modelMap[existing.cid] = true; set.push(existing); } models[i] = existing; // If this is a new, valid model, push it to the `toAdd` list. } else if (add) { model = models[i] = this._prepareModel(model, options); if (model) { toAdd.push(model); this._addReference(model, options); modelMap[model.cid] = true; set.push(model); } } } // Remove stale models. if (remove) { for (i = 0; i < this.length; i++) { model = this.models[i]; if (!modelMap[model.cid]) toRemove.push(model); } if (toRemove.length) this._removeModels(toRemove, options); } // See if sorting is needed, update `length` and splice in new models. var orderChanged = false; var replace = !sortable && add && remove; if (set.length && replace) { orderChanged = this.length !== set.length || _.some(this.models, function(m, index) { return m !== set[index]; }); this.models.length = 0; splice(this.models, set, 0); this.length = this.models.length; } else if (toAdd.length) { if (sortable) sort = true; splice(this.models, toAdd, at == null ? this.length : at); this.length = this.models.length; } // Silently sort the collection if appropriate. if (sort) this.sort({silent: true}); // Unless silenced, it's time to fire all appropriate add/sort/update events. if (!options.silent) { for (i = 0; i < toAdd.length; i++) { if (at != null) options.index = at + i; model = toAdd[i]; model.trigger('add', model, this, options); } if (sort || orderChanged) this.trigger('sort', this, options); if (toAdd.length || toRemove.length || toMerge.length) { options.changes = { added: toAdd, removed: toRemove, merged: toMerge }; this.trigger('update', this, options); } } // Return the added (or merged) model (or models). return singular ? models[0] : models; }, // When you have more items than you want to add or remove individually, // you can reset the entire set with a new list of models, without firing // any granular `add` or `remove` events. Fires `reset` when finished. // Useful for bulk operations and optimizations. reset: function(models, options) { options = options ? _.clone(options) : {}; for (var i = 0; i < this.models.length; i++) { this._removeReference(this.models[i], options); } options.previousModels = this.models; this._reset(); models = this.add(models, _.extend({silent: true}, options)); if (!options.silent) this.trigger('reset', this, options); return models; }, // Add a model to the end of the collection. push: function(model, options) { return this.add(model, _.extend({at: this.length}, options)); }, // Remove a model from the end of the collection. pop: function(options) { var model = this.at(this.length - 1); return this.remove(model, options); }, // Add a model to the beginning of the collection. unshift: function(model, options) { return this.add(model, _.extend({at: 0}, options)); }, // Remove a model from the beginning of the collection. shift: function(options) { var model = this.at(0); return this.remove(model, options); }, // Slice out a sub-array of models from the collection. slice: function() { return slice.apply(this.models, arguments); }, // Get a model from the set by id, cid, model object with id or cid // properties, or an attributes object that is transformed through modelId. get: function(obj) { if (obj == null) return void 0; return this._byId[obj] || this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj)] || obj.cid && this._byId[obj.cid]; }, // Returns `true` if the model is in the collection. has: function(obj) { return this.get(obj) != null; }, // Get the model at the given index. at: function(index) { if (index < 0) index += this.length; return this.models[index]; }, // Return models with matching attributes. Useful for simple cases of // `filter`. where: function(attrs, first) { return this[first ? 'find' : 'filter'](attrs); }, // Return the first model with matching attributes. Useful for simple cases // of `find`. findWhere: function(attrs) { return this.where(attrs, true); }, // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. sort: function(options) { var comparator = this.comparator; if (!comparator) throw new Error('Cannot sort a set without a comparator'); options || (options = {}); var length = comparator.length; if (_.isFunction(comparator)) comparator = comparator.bind(this); // Run sort based on type of `comparator`. if (length === 1 || _.isString(comparator)) { this.models = this.sortBy(comparator); } else { this.models.sort(comparator); } if (!options.silent) this.trigger('sort', this, options); return this; }, // Pluck an attribute from each model in the collection. pluck: function(attr) { return this.map(attr + ''); }, // Fetch the default set of models for this collection, resetting the // collection when they arrive. If `reset: true` is passed, the response // data will be passed through the `reset` method instead of `set`. fetch: function(options) { options = _.extend({parse: true}, options); var success = options.success; var collection = this; options.success = function(resp) { var method = options.reset ? 'reset' : 'set'; collection[method](resp, options); if (success) success.call(options.context, collection, resp, options); collection.trigger('sync', collection, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, // Create a new instance of a model in this collection. Add the model to the // collection immediately, unless `wait: true` is passed, in which case we // wait for the server to agree. create: function(model, options) { options = options ? _.clone(options) : {}; var wait = options.wait; model = this._prepareModel(model, options); if (!model) return false; if (!wait) this.add(model, options); var collection = this; var success = options.success; options.success = function(m, resp, callbackOpts) { if (wait) collection.add(m, callbackOpts); if (success) success.call(callbackOpts.context, m, resp, callbackOpts); }; model.save(null, options); return model; }, // **parse** converts a response into a list of models to be added to the // collection. The default implementation is just to pass it through. parse: function(resp, options) { return resp; }, // Create a new collection with an identical list of models as this one. clone: function() { return new this.constructor(this.models, { model: this.model, comparator: this.comparator }); }, // Define how to uniquely identify models in the collection. modelId: function(attrs) { return attrs[this.model.prototype.idAttribute || 'id']; }, // Get an iterator of all models in this collection. values: function() { return new CollectionIterator(this, ITERATOR_VALUES); }, // Get an iterator of all model IDs in this collection. keys: function() { return new CollectionIterator(this, ITERATOR_KEYS); }, // Get an iterator of all [ID, model] tuples in this collection. entries: function() { return new CollectionIterator(this, ITERATOR_KEYSVALUES); }, // Private method to reset all internal state. Called when the collection // is first initialized or reset. _reset: function() { this.length = 0; this.models = []; this._byId = {}; }, // Prepare a hash of attributes (or other model) to be added to this // collection. _prepareModel: function(attrs, options) { if (this._isModel(attrs)) { if (!attrs.collection) attrs.collection = this; return attrs; } options = options ? _.clone(options) : {}; options.collection = this; var model = new this.model(attrs, options); if (!model.validationError) return model; this.trigger('invalid', this, model.validationError, options); return false; }, // Internal method called by both remove and set. _removeModels: function(models, options) { var removed = []; for (var i = 0; i < models.length; i++) { var model = this.get(models[i]); if (!model) continue; var index = this.indexOf(model); this.models.splice(index, 1); this.length--; // Remove references before triggering 'remove' event to prevent an // infinite loop. #3693 delete this._byId[model.cid]; var id = this.modelId(model.attributes); if (id != null) delete this._byId[id]; if (!options.silent) { options.index = index; model.trigger('remove', model, this, options); } removed.push(model); this._removeReference(model, options); } return removed; }, // Method for checking whether an object should be considered a model for // the purposes of adding to the collection. _isModel: function(model) { return model instanceof Model; }, // Internal method to create a model's ties to a collection. _addReference: function(model, options) { this._byId[model.cid] = model; var id = this.modelId(model.attributes); if (id != null) this._byId[id] = model; model.on('all', this._onModelEvent, this); }, // Internal method to sever a model's ties to a collection. _removeReference: function(model, options) { delete this._byId[model.cid]; var id = this.modelId(model.attributes); if (id != null) delete this._byId[id]; if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); }, // Internal method called every time a model in the set fires an event. // Sets need to update their indexes when models change ids. All other // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. _onModelEvent: function(event, model, collection, options) { if (model) { if ((event === 'add' || event === 'remove') && collection !== this) return; if (event === 'destroy') this.remove(model, options); if (event === 'change') { var prevId = this.modelId(model.previousAttributes()); var id = this.modelId(model.attributes); if (prevId !== id) { if (prevId != null) delete this._byId[prevId]; if (id != null) this._byId[id] = model; } } } this.trigger.apply(this, arguments); } }); // Defining an @@iterator method implements JavaScript's Iterable protocol. // In modern ES2015 browsers, this value is found at Symbol.iterator. /* global Symbol */ var $$iterator = typeof Symbol === 'function' && Symbol.iterator; if ($$iterator) { Collection.prototype[$$iterator] = Collection.prototype.values; } // CollectionIterator // ------------------ // A CollectionIterator implements JavaScript's Iterator protocol, allowing the // use of `for of` loops in modern browsers and interoperation between // Backbone.Collection and other JavaScript functions and third-party libraries // which can operate on Iterables. var CollectionIterator = function(collection, kind) { this._collection = collection; this._kind = kind; this._index = 0; }; // This "enum" defines the three possible kinds of values which can be emitted // by a CollectionIterator that correspond to the values(), keys() and entries() // methods on Collection, respectively. var ITERATOR_VALUES = 1; var ITERATOR_KEYS = 2; var ITERATOR_KEYSVALUES = 3; // All Iterators should themselves be Iterable. if ($$iterator) { CollectionIterator.prototype[$$iterator] = function() { return this; }; } CollectionIterator.prototype.next = function() { if (this._collection) { // Only continue iterating if the iterated collection is long enough. if (this._index < this._collection.length) { var model = this._collection.at(this._index); this._index++; // Construct a value depending on what kind of values should be iterated. var value; if (this._kind === ITERATOR_VALUES) { value = model; } else { var id = this._collection.modelId(model.attributes); if (this._kind === ITERATOR_KEYS) { value = id; } else { // ITERATOR_KEYSVALUES value = [id, model]; } } return {value: value, done: false}; } // Once exhausted, remove the reference to the collection so future // calls to the next method always return done. this._collection = void 0; } return {value: void 0, done: true}; }; // Backbone.View // ------------- // Backbone Views are almost more convention than they are actual code. A View // is simply a JavaScript object that represents a logical chunk of UI in the // DOM. This might be a single item, an entire list, a sidebar or panel, or // even the surrounding frame which wraps your whole app. Defining a chunk of // UI as a **View** allows you to define your DOM events declaratively, without // having to worry about render order ... and makes it easy for the view to // react to specific changes in the state of your models. // Creating a Backbone.View creates its initial element outside of the DOM, // if an existing element is not provided... var View = Backbone.View = function(options) { this.cid = _.uniqueId('view'); this.preinitialize.apply(this, arguments); _.extend(this, _.pick(options, viewOptions)); this._ensureElement(); this.initialize.apply(this, arguments); }; // Cached regex to split keys for `delegate`. var delegateEventSplitter = /^(\S+)\s*(.*)$/; // List of view options to be set as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; // Set up all inheritable **Backbone.View** properties and methods. _.extend(View.prototype, Events, { // The default `tagName` of a View's element is `"div"`. tagName: 'div', // jQuery delegate for element lookup, scoped to DOM elements within the // current view. This should be preferred to global lookups where possible. $: function(selector) { return this.$el.find(selector); }, // preinitialize is an empty function by default. You can override it with a function // or object. preinitialize will run before any instantiation logic is run in the View preinitialize: function(){}, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // **render** is the core function that your view should override, in order // to populate its element (`this.el`), with the appropriate HTML. The // convention is for **render** to always return `this`. render: function() { return this; }, // Remove this view by taking the element out of the DOM, and removing any // applicable Backbone.Events listeners. remove: function() { this._removeElement(); this.stopListening(); return this; }, // Remove this view's element from the document and all event listeners // attached to it. Exposed for subclasses using an alternative DOM // manipulation API. _removeElement: function() { this.$el.remove(); }, // Change the view's element (`this.el` property) and re-delegate the // view's events on the new element. setElement: function(element) { this.undelegateEvents(); this._setElement(element); this.delegateEvents(); return this; }, // Creates the `this.el` and `this.$el` references for this view using the // given `el`. `el` can be a CSS selector or an HTML string, a jQuery // context or an element. Subclasses can override this to utilize an // alternative DOM manipulation API and are only required to set the // `this.el` property. _setElement: function(el) { this.$el = el instanceof Backbone.$ ? el : Backbone.$(el); this.el = this.$el[0]; }, // Set callbacks, where `this.events` is a hash of // // *{"event selector": "callback"}* // // { // 'mousedown .title': 'edit', // 'click .button': 'save', // 'click .open': function(e) { ... } // } // // pairs. Callbacks will be bound to the view, with `this` set properly. // Uses event delegation for efficiency. // Omitting the selector binds the event to `this.el`. delegateEvents: function(events) { events || (events = _.result(this, 'events')); if (!events) return this; this.undelegateEvents(); for (var key in events) { var method = events[key]; if (!_.isFunction(method)) method = this[method]; if (!method) continue; var match = key.match(delegateEventSplitter); this.delegate(match[1], match[2], method.bind(this)); } return this; }, // Add a single event listener to the view's element (or a child element // using `selector`). This only works for delegate-able events: not `focus`, // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer. delegate: function(eventName, selector, listener) { this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener); return this; }, // Clears all callbacks previously bound to the view by `delegateEvents`. // You usually don't need to use this, but may wish to if you have multiple // Backbone views attached to the same DOM element. undelegateEvents: function() { if (this.$el) this.$el.off('.delegateEvents' + this.cid); return this; }, // A finer-grained `undelegateEvents` for removing a single delegated event. // `selector` and `listener` are both optional. undelegate: function(eventName, selector, listener) { this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener); return this; }, // Produces a DOM element to be assigned to your view. Exposed for // subclasses using an alternative DOM manipulation API. _createElement: function(tagName) { return document.createElement(tagName); }, // Ensure that the View has a DOM element to render into. // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` properties. _ensureElement: function() { if (!this.el) { var attrs = _.extend({}, _.result(this, 'attributes')); if (this.id) attrs.id = _.result(this, 'id'); if (this.className) attrs['class'] = _.result(this, 'className'); this.setElement(this._createElement(_.result(this, 'tagName'))); this._setAttributes(attrs); } else { this.setElement(_.result(this, 'el')); } }, // Set attributes from a hash on this view's element. Exposed for // subclasses using an alternative DOM manipulation API. _setAttributes: function(attributes) { this.$el.attr(attributes); } }); // Proxy Backbone class methods to Underscore functions, wrapping the model's // `attributes` object or collection's `models` array behind the scenes. // // collection.filter(function(model) { return model.get('age') > 10 }); // collection.each(this.addView); // // `Function#apply` can be slow so we use the method's arg count, if we know it. var addMethod = function(base, length, method, attribute) { switch (length) { case 1: return function() { return base[method](this[attribute]); }; case 2: return function(value) { return base[method](this[attribute], value); }; case 3: return function(iteratee, context) { return base[method](this[attribute], cb(iteratee, this), context); }; case 4: return function(iteratee, defaultVal, context) { return base[method](this[attribute], cb(iteratee, this), defaultVal, context); }; default: return function() { var args = slice.call(arguments); args.unshift(this[attribute]); return base[method].apply(base, args); }; } }; var addUnderscoreMethods = function(Class, base, methods, attribute) { _.each(methods, function(length, method) { if (base[method]) Class.prototype[method] = addMethod(base, length, method, attribute); }); }; // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. var cb = function(iteratee, instance) { if (_.isFunction(iteratee)) return iteratee; if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; return iteratee; }; var modelMatcher = function(attrs) { var matcher = _.matches(attrs); return function(model) { return matcher(model.attributes); }; }; // Underscore methods that we want to implement on the Collection. // 90% of the core usefulness of Backbone Collections is actually implemented // right here: var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0, foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; // Underscore methods that we want to implement on the Model, mapped to the // number of arguments they take. var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, omit: 0, chain: 1, isEmpty: 1}; // Mix in each Underscore method as a proxy to `Collection#models`. _.each([ [Collection, collectionMethods, 'models'], [Model, modelMethods, 'attributes'] ], function(config) { var Base = config[0], methods = config[1], attribute = config[2]; Base.mixin = function(obj) { var mappings = _.reduce(_.functions(obj), function(memo, name) { memo[name] = 0; return memo; }, {}); addUnderscoreMethods(Base, obj, mappings, attribute); }; addUnderscoreMethods(Base, _, methods, attribute); }); // Backbone.sync // ------------- // Override this function to change the manner in which Backbone persists // models to the server. You will be passed the type of request, and the // model in question. By default, makes a RESTful Ajax request // to the model's `url()`. Some possible customizations could be: // // * Use `setTimeout` to batch rapid-fire updates into a single request. // * Send up the models as XML instead of JSON. // * Persist models via WebSockets instead of Ajax. // // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests // as `POST`, with a `_method` parameter containing the true HTTP method, // as well as all requests with the body as `application/x-www-form-urlencoded` // instead of `application/json` with the model in a param named `model`. // Useful when interfacing with server-side languages like **PHP** that make // it difficult to read the body of `PUT` requests. Backbone.sync = function(method, model, options) { var type = methodMap[method]; // Default options, unless specified. _.defaults(options || (options = {}), { emulateHTTP: Backbone.emulateHTTP, emulateJSON: Backbone.emulateJSON }); // Default JSON-request options. var params = {type: type, dataType: 'json'}; // Ensure that we have a URL. if (!options.url) { params.url = _.result(model, 'url') || urlError(); } // Ensure that we have the appropriate request data. if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { params.contentType = 'application/json'; params.data = JSON.stringify(options.attrs || model.toJSON(options)); } // For older servers, emulate JSON by encoding the request into an HTML-form. if (options.emulateJSON) { params.contentType = 'application/x-www-form-urlencoded'; params.data = params.data ? {model: params.data} : {}; } // For older servers, emulate HTTP by mimicking the HTTP method with `_method` // And an `X-HTTP-Method-Override` header. if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { params.type = 'POST'; if (options.emulateJSON) params.data._method = type; var beforeSend = options.beforeSend; options.beforeSend = function(xhr) { xhr.setRequestHeader('X-HTTP-Method-Override', type); if (beforeSend) return beforeSend.apply(this, arguments); }; } // Don't process data on a non-GET request. if (params.type !== 'GET' && !options.emulateJSON) { params.processData = false; } // Pass along `textStatus` and `errorThrown` from jQuery. var error = options.error; options.error = function(xhr, textStatus, errorThrown) { options.textStatus = textStatus; options.errorThrown = errorThrown; if (error) error.call(options.context, xhr, textStatus, errorThrown); }; // Make the request, allowing the user to override any Ajax options. var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); model.trigger('request', model, xhr, options); return xhr; }; // Map from CRUD to HTTP for our default `Backbone.sync` implementation. var methodMap = { create: 'POST', update: 'PUT', patch: 'PATCH', delete: 'DELETE', read: 'GET' }; // Set the default implementation of `Backbone.ajax` to proxy through to `$`. // Override this if you'd like to use a different library. Backbone.ajax = function() { return Backbone.$.ajax.apply(Backbone.$, arguments); }; // Backbone.Router // --------------- // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. var Router = Backbone.Router = function(options) { options || (options = {}); this.preinitialize.apply(this, arguments); if (options.routes) this.routes = options.routes; this._bindRoutes(); this.initialize.apply(this, arguments); }; // Cached regular expressions for matching named param parts and splatted // parts of route strings. var optionalParam = /\((.*?)\)/g; var namedParam = /(\(\?)?:\w+/g; var splatParam = /\*\w+/g; var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // Set up all inheritable **Backbone.Router** properties and methods. _.extend(Router.prototype, Events, { // preinitialize is an empty function by default. You can override it with a function // or object. preinitialize will run before any instantiation logic is run in the Router. preinitialize: function(){}, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // Manually bind a single named route to a callback. For example: // // this.route('search/:query/p:num', 'search', function(query, num) { // ... // }); // route: function(route, name, callback) { if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (_.isFunction(name)) { callback = name; name = ''; } if (!callback) callback = this[name]; var router = this; Backbone.history.route(route, function(fragment) { var args = router._extractParameters(route, fragment); if (router.execute(callback, args, name) !== false) { router.trigger.apply(router, ['route:' + name].concat(args)); router.trigger('route', name, args); Backbone.history.trigger('route', router, name, args); } }); return this; }, // Execute a route handler with the provided parameters. This is an // excellent place to do pre-route setup or post-route cleanup. execute: function(callback, args, name) { if (callback) callback.apply(this, args); }, // Simple proxy to `Backbone.history` to save a fragment into the history. navigate: function(fragment, options) { Backbone.history.navigate(fragment, options); return this; }, // Bind all defined routes to `Backbone.history`. We have to reverse the // order of the routes here to support behavior where the most general // routes can be defined at the bottom of the route map. _bindRoutes: function() { if (!this.routes) return; this.routes = _.result(this, 'routes'); var route, routes = _.keys(this.routes); while ((route = routes.pop()) != null) { this.route(route, this.routes[route]); } }, // Convert a route string into a regular expression, suitable for matching // against the current location hash. _routeToRegExp: function(route) { route = route.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') .replace(namedParam, function(match, optional) { return optional ? match : '([^/?]+)'; }) .replace(splatParam, '([^?]*?)'); return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); }, // Given a route, and a URL fragment that it matches, return the array of // extracted decoded parameters. Empty or unmatched parameters will be // treated as `null` to normalize cross-browser behavior. _extractParameters: function(route, fragment) { var params = route.exec(fragment).slice(1); return _.map(params, function(param, i) { // Don't decode the search params. if (i === params.length - 1) return param || null; return param ? decodeURIComponent(param) : null; }); } }); // Backbone.History // ---------------- // Handles cross-browser history management, based on either // [pushState](http://diveintohtml5.info/history.html) and real URLs, or // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) // and URL fragments. If the browser supports neither (old IE, natch), // falls back to polling. var History = Backbone.History = function() { this.handlers = []; this.checkUrl = this.checkUrl.bind(this); // Ensure that `History` can be used outside of the browser. if (typeof window !== 'undefined') { this.location = window.location; this.history = window.history; } }; // Cached regex for stripping a leading hash/slash and trailing space. var routeStripper = /^[#\/]|\s+$/g; // Cached regex for stripping leading and trailing slashes. var rootStripper = /^\/+|\/+$/g; // Cached regex for stripping urls of hash. var pathStripper = /#.*$/; // Has the history handling already been started? History.started = false; // Set up all inheritable **Backbone.History** properties and methods. _.extend(History.prototype, Events, { // The default interval to poll for hash changes, if necessary, is // twenty times a second. interval: 50, // Are we at the app root? atRoot: function() { var path = this.location.pathname.replace(/[^\/]$/, '$&/'); return path === this.root && !this.getSearch(); }, // Does the pathname match the root? matchRoot: function() { var path = this.decodeFragment(this.location.pathname); var rootPath = path.slice(0, this.root.length - 1) + '/'; return rootPath === this.root; }, // Unicode characters in `location.pathname` are percent encoded so they're // decoded for comparison. `%25` should not be decoded since it may be part // of an encoded parameter. decodeFragment: function(fragment) { return decodeURI(fragment.replace(/%25/g, '%2525')); }, // In IE6, the hash fragment and search params are incorrect if the // fragment contains `?`. getSearch: function() { var match = this.location.href.replace(/#.*/, '').match(/\?.+/); return match ? match[0] : ''; }, // Gets the true hash value. Cannot use location.hash directly due to bug // in Firefox where location.hash will always be decoded. getHash: function(window) { var match = (window || this).location.href.match(/#(.*)$/); return match ? match[1] : ''; }, // Get the pathname and search params, without the root. getPath: function() { var path = this.decodeFragment( this.location.pathname + this.getSearch() ).slice(this.root.length - 1); return path.charAt(0) === '/' ? path.slice(1) : path; }, // Get the cross-browser normalized URL fragment from the path or hash. getFragment: function(fragment) { if (fragment == null) { if (this._usePushState || !this._wantsHashChange) { fragment = this.getPath(); } else { fragment = this.getHash(); } } return fragment.replace(routeStripper, ''); }, // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. start: function(options) { if (History.started) throw new Error('Backbone.history has already been started'); History.started = true; // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? this.options = _.extend({root: '/'}, this.options, options); this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7); this._useHashChange = this._wantsHashChange && this._hasHashChange; this._wantsPushState = !!this.options.pushState; this._hasPushState = !!(this.history && this.history.pushState); this._usePushState = this._wantsPushState && this._hasPushState; this.fragment = this.getFragment(); // Normalize root to always include a leading and trailing slash. this.root = ('/' + this.root + '/').replace(rootStripper, '/'); // Transition from hashChange to pushState or vice versa if both are // requested. if (this._wantsHashChange && this._wantsPushState) { // If we've started off with a route from a `pushState`-enabled // browser, but we're currently in a browser that doesn't support it... if (!this._hasPushState && !this.atRoot()) { var rootPath = this.root.slice(0, -1) || '/'; this.location.replace(rootPath + '#' + this.getPath()); // Return immediately as browser will do redirect to new url return true; // Or if we've started out with a hash-based route, but we're currently // in a browser where it could be `pushState`-based instead... } else if (this._hasPushState && this.atRoot()) { this.navigate(this.getHash(), {replace: true}); } } // Proxy an iframe to handle location events if the browser doesn't // support the `hashchange` event, HTML5 history, or the user wants // `hashChange` but not `pushState`. if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) { this.iframe = document.createElement('iframe'); this.iframe.src = 'javascript:0'; this.iframe.style.display = 'none'; this.iframe.tabIndex = -1; var body = document.body; // Using `appendChild` will throw on IE < 9 if the document is not ready. var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow; iWindow.document.open(); iWindow.document.close(); iWindow.location.hash = '#' + this.fragment; } // Add a cross-platform `addEventListener` shim for older browsers. var addEventListener = window.addEventListener || function(eventName, listener) { return attachEvent('on' + eventName, listener); }; // Depending on whether we're using pushState or hashes, and whether // 'onhashchange' is supported, determine how we check the URL state. if (this._usePushState) { addEventListener('popstate', this.checkUrl, false); } else if (this._useHashChange && !this.iframe) { addEventListener('hashchange', this.checkUrl, false); } else if (this._wantsHashChange) { this._checkUrlInterval = setInterval(this.checkUrl, this.interval); } if (!this.options.silent) return this.loadUrl(); }, // Disable Backbone.history, perhaps temporarily. Not useful in a real app, // but possibly useful for unit testing Routers. stop: function() { // Add a cross-platform `removeEventListener` shim for older browsers. var removeEventListener = window.removeEventListener || function(eventName, listener) { return detachEvent('on' + eventName, listener); }; // Remove window listeners. if (this._usePushState) { removeEventListener('popstate', this.checkUrl, false); } else if (this._useHashChange && !this.iframe) { removeEventListener('hashchange', this.checkUrl, false); } // Clean up the iframe if necessary. if (this.iframe) { document.body.removeChild(this.iframe); this.iframe = null; } // Some environments will throw when clearing an undefined interval. if (this._checkUrlInterval) clearInterval(this._checkUrlInterval); History.started = false; }, // Add a route to be tested when the fragment changes. Routes added later // may override previous routes. route: function(route, callback) { this.handlers.unshift({route: route, callback: callback}); }, // Checks the current URL to see if it has changed, and if it has, // calls `loadUrl`, normalizing across the hidden iframe. checkUrl: function(e) { var current = this.getFragment(); // If the user pressed the back button, the iframe's hash will have // changed and we should use that for comparison. if (current === this.fragment && this.iframe) { current = this.getHash(this.iframe.contentWindow); } if (current === this.fragment) return false; if (this.iframe) this.navigate(current); this.loadUrl(); }, // Attempt to load the current URL fragment. If a route succeeds with a // match, returns `true`. If no defined routes matches the fragment, // returns `false`. loadUrl: function(fragment) { // If the root doesn't match, no routes can match either. if (!this.matchRoot()) return false; fragment = this.fragment = this.getFragment(fragment); return _.some(this.handlers, function(handler) { if (handler.route.test(fragment)) { handler.callback(fragment); return true; } }); }, // Save a fragment into the hash history, or replace the URL state if the // 'replace' option is passed. You are responsible for properly URL-encoding // the fragment in advance. // // The options object can contain `trigger: true` if you wish to have the // route callback be fired (not usually desirable), or `replace: true`, if // you wish to modify the current URL without adding an entry to the history. navigate: function(fragment, options) { if (!History.started) return false; if (!options || options === true) options = {trigger: !!options}; // Normalize the fragment. fragment = this.getFragment(fragment || ''); // Don't include a trailing slash on the root. var rootPath = this.root; if (fragment === '' || fragment.charAt(0) === '?') { rootPath = rootPath.slice(0, -1) || '/'; } var url = rootPath + fragment; // Strip the fragment of the query and hash for matching. fragment = fragment.replace(pathStripper, ''); // Decode for matching. var decodedFragment = this.decodeFragment(fragment); if (this.fragment === decodedFragment) return; this.fragment = decodedFragment; // If pushState is available, we use it to set the fragment as a real URL. if (this._usePushState) { this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); // If hash changes haven't been explicitly disabled, update the hash // fragment to store history. } else if (this._wantsHashChange) { this._updateHash(this.location, fragment, options.replace); if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) { var iWindow = this.iframe.contentWindow; // Opening and closing the iframe tricks IE7 and earlier to push a // history entry on hash-tag change. When replace is true, we don't // want this. if (!options.replace) { iWindow.document.open(); iWindow.document.close(); } this._updateHash(iWindow.location, fragment, options.replace); } // If you've told us that you explicitly don't want fallback hashchange- // based history, then `navigate` becomes a page refresh. } else { return this.location.assign(url); } if (options.trigger) return this.loadUrl(fragment); }, // Update the hash location, either replacing the current entry, or adding // a new one to the browser history. _updateHash: function(location, fragment, replace) { if (replace) { var href = location.href.replace(/(javascript:|#).*$/, ''); location.replace(href + '#' + fragment); } else { // Some browsers require that `hash` contains a leading #. location.hash = '#' + fragment; } } }); // Create the default Backbone.history. Backbone.history = new History; // Helpers // ------- // Helper function to correctly set up the prototype chain for subclasses. // Similar to `goog.inherits`, but uses a hash of prototype properties and // class properties to be extended. var extend = function(protoProps, staticProps) { var parent = this; var child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent constructor. if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { child = function(){ return parent.apply(this, arguments); }; } // Add static properties to the constructor function, if supplied. _.extend(child, parent, staticProps); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function and add the prototype properties. child.prototype = _.create(parent.prototype, protoProps); child.prototype.constructor = child; // Set a convenience property in case the parent's prototype is needed // later. child.__super__ = parent.prototype; return child; }; // Set up inheritance for the model, collection, router, view and history. Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; // Throw an error when a URL is needed, and none is supplied. var urlError = function() { throw new Error('A "url" property or function must be specified'); }; // Wrap an optional error callback with a fallback error event. var wrapError = function(model, options) { var error = options.error; options.error = function(resp) { if (error) error.call(options.context, model, resp, options); model.trigger('error', model, resp, options); }; }; return Backbone; }); ; /** * @output wp-includes/js/wp-util.js */ /* global _wpUtilSettings */ /** @namespace wp */ window.wp = window.wp || {}; (function ($) { // Check for the utility settings. var settings = typeof _wpUtilSettings === 'undefined' ? {} : _wpUtilSettings; /** * wp.template( id ) * * Fetch a JavaScript template for an id, and return a templating function for it. * * @param {string} id A string that corresponds to a DOM element with an id prefixed with "tmpl-". * For example, "attachment" maps to "tmpl-attachment". * @return {function} A function that lazily-compiles the template requested. */ wp.template = _.memoize(function ( id ) { var compiled, /* * Underscore's default ERB-style templates are incompatible with PHP * when asp_tags is enabled, so WordPress uses Mustache-inspired templating syntax. * * @see trac ticket #22344. */ options = { evaluate: /<#([\s\S]+?)#>/g, interpolate: /\{\{\{([\s\S]+?)\}\}\}/g, escape: /\{\{([^\}]+?)\}\}(?!\})/g, variable: 'data' }; return function ( data ) { compiled = compiled || _.template( $( '#tmpl-' + id ).html(), options ); return compiled( data ); }; }); // wp.ajax // ------ // // Tools for sending ajax requests with JSON responses and built in error handling. // Mirrors and wraps jQuery's ajax APIs. wp.ajax = { settings: settings.ajax || {}, /** * wp.ajax.post( [action], [data] ) * * Sends a POST request to WordPress. * * @param {(string|object)} action The slug of the action to fire in WordPress or options passed * to jQuery.ajax. * @param {object=} data Optional. The data to populate $_POST with. * @return {$.promise} A jQuery promise that represents the request, * decorated with an abort() method. */ post: function( action, data ) { return wp.ajax.send({ data: _.isObject( action ) ? action : _.extend( data || {}, { action: action }) }); }, /** * wp.ajax.send( [action], [options] ) * * Sends a POST request to WordPress. * * @param {(string|object)} action The slug of the action to fire in WordPress or options passed * to jQuery.ajax. * @param {object=} options Optional. The options passed to jQuery.ajax. * @return {$.promise} A jQuery promise that represents the request, * decorated with an abort() method. */ send: function( action, options ) { var promise, deferred; if ( _.isObject( action ) ) { options = action; } else { options = options || {}; options.data = _.extend( options.data || {}, { action: action }); } options = _.defaults( options || {}, { type: 'POST', url: wp.ajax.settings.url, context: this }); deferred = $.Deferred( function( deferred ) { // Transfer success/error callbacks. if ( options.success ) deferred.done( options.success ); if ( options.error ) deferred.fail( options.error ); delete options.success; delete options.error; // Use with PHP's wp_send_json_success() and wp_send_json_error() deferred.jqXHR = $.ajax( options ).done( function( response ) { // Treat a response of 1 as successful for backward compatibility with existing handlers. if ( response === '1' || response === 1 ) response = { success: true }; if ( _.isObject( response ) && ! _.isUndefined( response.success ) ) deferred[ response.success ? 'resolveWith' : 'rejectWith' ]( this, [response.data] ); else deferred.rejectWith( this, [response] ); }).fail( function() { deferred.rejectWith( this, arguments ); }); }); promise = deferred.promise(); promise.abort = function() { deferred.jqXHR.abort(); return this; }; return promise; } }; }(jQuery)); ; /** * @output wp-includes/js/wp-backbone.js */ /** @namespace wp */ window.wp = window.wp || {}; (function ($) { /** * Create the WordPress Backbone namespace. * * @namespace wp.Backbone */ wp.Backbone = {}; /** * A backbone subview manager. * * @since 3.5.0 * @since 3.6.0 Moved wp.media.Views to wp.Backbone.Subviews. * * @memberOf wp.Backbone * * @class * * @param {wp.Backbone.View} view The main view. * @param {Array|Object} views The subviews for the main view. */ wp.Backbone.Subviews = function( view, views ) { this.view = view; this._views = _.isArray( views ) ? { '': views } : views || {}; }; wp.Backbone.Subviews.extend = Backbone.Model.extend; _.extend( wp.Backbone.Subviews.prototype, { /** * Fetches all of the subviews. * * @since 3.5.0 * * @return {Array} All the subviews. */ all: function() { return _.flatten( _.values( this._views ) ); }, /** * Fetches all subviews that match a given `selector`. * * If no `selector` is provided, it will grab all subviews attached * to the view's root. * * @since 3.5.0 * * @param {string} selector A jQuery selector. * * @return {Array} All the subviews that match the selector. */ get: function( selector ) { selector = selector || ''; return this._views[ selector ]; }, /** * Fetches the first subview that matches a given `selector`. * * If no `selector` is provided, it will grab the first subview attached to the * view's root. * * Useful when a selector only has one subview at a time. * * @since 3.5.0 * * @param {string} selector A jQuery selector. * * @return {Backbone.View} The view. */ first: function( selector ) { var views = this.get( selector ); return views && views.length ? views[0] : null; }, /** * Registers subview(s). * * Registers any number of `views` to a `selector`. * * When no `selector` is provided, the root selector (the empty string) * is used. `views` accepts a `Backbone.View` instance or an array of * `Backbone.View` instances. * * --- * * Accepts an `options` object, which has a significant effect on the * resulting behavior. * * `options.silent` - *boolean, `false`* * If `options.silent` is true, no DOM modifications will be made. * * `options.add` - *boolean, `false`* * Use `Views.add()` as a shortcut for setting `options.add` to true. * * By default, the provided `views` will replace any existing views * associated with the selector. If `options.add` is true, the provided * `views` will be added to the existing views. * * `options.at` - *integer, `undefined`* * When adding, to insert `views` at a specific index, use `options.at`. * By default, `views` are added to the end of the array. * * @since 3.5.0 * * @param {string} selector A jQuery selector. * @param {Array|Object} views The subviews for the main view. * @param {Object} options Options for call. If `options.silent` is true, * no DOM modifications will be made. Use * `Views.add()` as a shortcut for setting * `options.add` to true. If `options.add` is * true, the provided `views` will be added to * the existing views. When adding, to insert * `views` at a specific index, use `options.at`. * * @return {wp.Backbone.Subviews} The current Subviews instance. */ set: function( selector, views, options ) { var existing, next; if ( ! _.isString( selector ) ) { options = views; views = selector; selector = ''; } options = options || {}; views = _.isArray( views ) ? views : [ views ]; existing = this.get( selector ); next = views; if ( existing ) { if ( options.add ) { if ( _.isUndefined( options.at ) ) { next = existing.concat( views ); } else { next = existing; next.splice.apply( next, [ options.at, 0 ].concat( views ) ); } } else { _.each( next, function( view ) { view.__detach = true; }); _.each( existing, function( view ) { if ( view.__detach ) view.$el.detach(); else view.remove(); }); _.each( next, function( view ) { delete view.__detach; }); } } this._views[ selector ] = next; _.each( views, function( subview ) { var constructor = subview.Views || wp.Backbone.Subviews, subviews = subview.views = subview.views || new constructor( subview ); subviews.parent = this.view; subviews.selector = selector; }, this ); if ( ! options.silent ) this._attach( selector, views, _.extend({ ready: this._isReady() }, options ) ); return this; }, /** * Add subview(s) to existing subviews. * * An alias to `Views.set()`, which defaults `options.add` to true. * * Adds any number of `views` to a `selector`. * * When no `selector` is provided, the root selector (the empty string) * is used. `views` accepts a `Backbone.View` instance or an array of * `Backbone.View` instances. * * Uses `Views.set()` when setting `options.add` to `false`. * * Accepts an `options` object. By default, provided `views` will be * inserted at the end of the array of existing views. To insert * `views` at a specific index, use `options.at`. If `options.silent` * is true, no DOM modifications will be made. * * For more information on the `options` object, see `Views.set()`. * * @since 3.5.0 * * @param {string} selector A jQuery selector. * @param {Array|Object} views The subviews for the main view. * @param {Object} options Options for call. To insert `views` at a * specific index, use `options.at`. If * `options.silent` is true, no DOM modifications * will be made. * * @return {wp.Backbone.Subviews} The current subviews instance. */ add: function( selector, views, options ) { if ( ! _.isString( selector ) ) { options = views; views = selector; selector = ''; } return this.set( selector, views, _.extend({ add: true }, options ) ); }, /** * Removes an added subview. * * Stops tracking `views` registered to a `selector`. If no `views` are * set, then all of the `selector`'s subviews will be unregistered and * removed. * * Accepts an `options` object. If `options.silent` is set, `remove` * will *not* be triggered on the unregistered views. * * @since 3.5.0 * * @param {string} selector A jQuery selector. * @param {Array|Object} views The subviews for the main view. * @param {Object} options Options for call. If `options.silent` is set, * `remove` will *not* be triggered on the * unregistered views. * * @return {wp.Backbone.Subviews} The current Subviews instance. */ unset: function( selector, views, options ) { var existing; if ( ! _.isString( selector ) ) { options = views; views = selector; selector = ''; } views = views || []; if ( existing = this.get( selector ) ) { views = _.isArray( views ) ? views : [ views ]; this._views[ selector ] = views.length ? _.difference( existing, views ) : []; } if ( ! options || ! options.silent ) _.invoke( views, 'remove' ); return this; }, /** * Detaches all subviews. * * Helps to preserve all subview events when re-rendering the master * view. Used in conjunction with `Views.render()`. * * @since 3.5.0 * * @return {wp.Backbone.Subviews} The current Subviews instance. */ detach: function() { $( _.pluck( this.all(), 'el' ) ).detach(); return this; }, /** * Renders all subviews. * * Used in conjunction with `Views.detach()`. * * @since 3.5.0 * * @return {wp.Backbone.Subviews} The current Subviews instance. */ render: function() { var options = { ready: this._isReady() }; _.each( this._views, function( views, selector ) { this._attach( selector, views, options ); }, this ); this.rendered = true; return this; }, /** * Removes all subviews. * * Triggers the `remove()` method on all subviews. Detaches the master * view from its parent. Resets the internals of the views manager. * * Accepts an `options` object. If `options.silent` is set, `unset` * will *not* be triggered on the master view's parent. * * @since 3.6.0 * * @param {Object} options Options for call. * @param {boolean} options.silent If true, `unset` wil *not* be triggered on * the master views' parent. * * @return {wp.Backbone.Subviews} The current Subviews instance. */ remove: function( options ) { if ( ! options || ! options.silent ) { if ( this.parent && this.parent.views ) this.parent.views.unset( this.selector, this.view, { silent: true }); delete this.parent; delete this.selector; } _.invoke( this.all(), 'remove' ); this._views = []; return this; }, /** * Replaces a selector's subviews * * By default, sets the `$target` selector's html to the subview `els`. * * Can be overridden in subclasses. * * @since 3.5.0 * * @param {string} $target Selector where to put the elements. * @param {*} els HTML or elements to put into the selector's HTML. * * @return {wp.Backbone.Subviews} The current Subviews instance. */ replace: function( $target, els ) { $target.html( els ); return this; }, /** * Insert subviews into a selector. * * By default, appends the subview `els` to the end of the `$target` * selector. If `options.at` is set, inserts the subview `els` at the * provided index. * * Can be overridden in subclasses. * * @since 3.5.0 * * @param {string} $target Selector where to put the elements. * @param {*} els HTML or elements to put at the end of the * $target. * @param {?Object} options Options for call. * @param {?number} options.at At which index to put the elements. * * @return {wp.Backbone.Subviews} The current Subviews instance. */ insert: function( $target, els, options ) { var at = options && options.at, $children; if ( _.isNumber( at ) && ($children = $target.children()).length > at ) $children.eq( at ).before( els ); else $target.append( els ); return this; }, /** * Triggers the ready event. * * Only use this method if you know what you're doing. For performance reasons, * this method does not check if the view is actually attached to the DOM. It's * taking your word for it. * * Fires the ready event on the current view and all attached subviews. * * @since 3.5.0 */ ready: function() { this.view.trigger('ready'); // Find all attached subviews, and call ready on them. _.chain( this.all() ).map( function( view ) { return view.views; }).flatten().where({ attached: true }).invoke('ready'); }, /** * Attaches a series of views to a selector. Internal. * * Checks to see if a matching selector exists, renders the views, * performs the proper DOM operation, and then checks if the view is * attached to the document. * * @since 3.5.0 * * @private * * @param {string} selector A jQuery selector. * @param {Array|Object} views The subviews for the main view. * @param {Object} options Options for call. * @param {boolean} options.add If true the provided views will be added. * * @return {wp.Backbone.Subviews} The current Subviews instance. */ _attach: function( selector, views, options ) { var $selector = selector ? this.view.$( selector ) : this.view.$el, managers; // Check if we found a location to attach the views. if ( ! $selector.length ) return this; managers = _.chain( views ).pluck('views').flatten().value(); // Render the views if necessary. _.each( managers, function( manager ) { if ( manager.rendered ) return; manager.view.render(); manager.rendered = true; }, this ); // Insert or replace the views. this[ options.add ? 'insert' : 'replace' ]( $selector, _.pluck( views, 'el' ), options ); /* * Set attached and trigger ready if the current view is already * attached to the DOM. */ _.each( managers, function( manager ) { manager.attached = true; if ( options.ready ) manager.ready(); }, this ); return this; }, /** * Determines whether or not the current view is in the DOM. * * @since 3.5.0 * * @private * * @return {boolean} Whether or not the current view is in the DOM. */ _isReady: function() { var node = this.view.el; while ( node ) { if ( node === document.body ) return true; node = node.parentNode; } return false; } }); wp.Backbone.View = Backbone.View.extend({ // The constructor for the `Views` manager. Subviews: wp.Backbone.Subviews, /** * The base view class. * * This extends the backbone view to have a build-in way to use subviews. This * makes it easier to have nested views. * * @since 3.5.0 * @since 3.6.0 Moved wp.media.View to wp.Backbone.View * * @constructs * @augments Backbone.View * * @memberOf wp.Backbone * * * @param {Object} options The options for this view. */ constructor: function( options ) { this.views = new this.Subviews( this, this.views ); this.on( 'ready', this.ready, this ); this.options = options || {}; Backbone.View.apply( this, arguments ); }, /** * Removes this view and all subviews. * * @since 3.5.0 * * @return {wp.Backbone.Subviews} The current Subviews instance. */ remove: function() { var result = Backbone.View.prototype.remove.apply( this, arguments ); // Recursively remove child views. if ( this.views ) this.views.remove(); return result; }, /** * Renders this view and all subviews. * * @since 3.5.0 * * @return {wp.Backbone.View} The current instance of the view. */ render: function() { var options; if ( this.prepare ) options = this.prepare(); this.views.detach(); if ( this.template ) { options = options || {}; this.trigger( 'prepare', options ); this.$el.html( this.template( options ) ); } this.views.render(); return this; }, /** * Returns the options for this view. * * @since 3.5.0 * * @return {Object} The options for this view. */ prepare: function() { return this.options; }, /** * Method that is called when the ready event is triggered. * * @since 3.5.0 */ ready: function() {} }); }(jQuery)); ; /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 22); /******/ }) /************************************************************************/ /******/ ({ /***/ 22: /***/ (function(module, exports, __webpack_require__) { module.exports = __webpack_require__(23); /***/ }), /***/ 23: /***/ (function(module, exports, __webpack_require__) { /** * @output wp-includes/js/media-models.js */ var $ = jQuery, Attachment, Attachments, l10n, media; /** @namespace wp */ window.wp = window.wp || {}; /** * Create and return a media frame. * * Handles the default media experience. * * @alias wp.media * @memberOf wp * @namespace * * @param {object} attributes The properties passed to the main media controller. * @return {wp.media.view.MediaFrame} A media workflow. */ media = wp.media = function( attributes ) { var MediaFrame = media.view.MediaFrame, frame; if ( ! MediaFrame ) { return; } attributes = _.defaults( attributes || {}, { frame: 'select' }); if ( 'select' === attributes.frame && MediaFrame.Select ) { frame = new MediaFrame.Select( attributes ); } else if ( 'post' === attributes.frame && MediaFrame.Post ) { frame = new MediaFrame.Post( attributes ); } else if ( 'manage' === attributes.frame && MediaFrame.Manage ) { frame = new MediaFrame.Manage( attributes ); } else if ( 'image' === attributes.frame && MediaFrame.ImageDetails ) { frame = new MediaFrame.ImageDetails( attributes ); } else if ( 'audio' === attributes.frame && MediaFrame.AudioDetails ) { frame = new MediaFrame.AudioDetails( attributes ); } else if ( 'video' === attributes.frame && MediaFrame.VideoDetails ) { frame = new MediaFrame.VideoDetails( attributes ); } else if ( 'edit-attachments' === attributes.frame && MediaFrame.EditAttachments ) { frame = new MediaFrame.EditAttachments( attributes ); } delete attributes.frame; media.frame = frame; return frame; }; /** @namespace wp.media.model */ /** @namespace wp.media.view */ /** @namespace wp.media.controller */ /** @namespace wp.media.frames */ _.extend( media, { model: {}, view: {}, controller: {}, frames: {} }); // Link any localized strings. l10n = media.model.l10n = window._wpMediaModelsL10n || {}; // Link any settings. media.model.settings = l10n.settings || {}; delete l10n.settings; Attachment = media.model.Attachment = __webpack_require__( 24 ); Attachments = media.model.Attachments = __webpack_require__( 25 ); media.model.Query = __webpack_require__( 26 ); media.model.PostImage = __webpack_require__( 27 ); media.model.Selection = __webpack_require__( 28 ); /** * ======================================================================== * UTILITIES * ======================================================================== */ /** * A basic equality comparator for Backbone models. * * Used to order models within a collection - @see wp.media.model.Attachments.comparator(). * * @param {mixed} a The primary parameter to compare. * @param {mixed} b The primary parameter to compare. * @param {string} ac The fallback parameter to compare, a's cid. * @param {string} bc The fallback parameter to compare, b's cid. * @return {number} -1: a should come before b. * 0: a and b are of the same rank. * 1: b should come before a. */ media.compare = function( a, b, ac, bc ) { if ( _.isEqual( a, b ) ) { return ac === bc ? 0 : (ac > bc ? -1 : 1); } else { return a > b ? -1 : 1; } }; _.extend( media, /** @lends wp.media */{ /** * media.template( id ) * * Fetch a JavaScript template for an id, and return a templating function for it. * * See wp.template() in `wp-includes/js/wp-util.js`. * * @borrows wp.template as template */ template: wp.template, /** * media.post( [action], [data] ) * * Sends a POST request to WordPress. * See wp.ajax.post() in `wp-includes/js/wp-util.js`. * * @borrows wp.ajax.post as post */ post: wp.ajax.post, /** * media.ajax( [action], [options] ) * * Sends an XHR request to WordPress. * See wp.ajax.send() in `wp-includes/js/wp-util.js`. * * @borrows wp.ajax.send as ajax */ ajax: wp.ajax.send, /** * Scales a set of dimensions to fit within bounding dimensions. * * @param {Object} dimensions * @returns {Object} */ fit: function( dimensions ) { var width = dimensions.width, height = dimensions.height, maxWidth = dimensions.maxWidth, maxHeight = dimensions.maxHeight, constraint; // Compare ratios between the two values to determine which // max to constrain by. If a max value doesn't exist, then the // opposite side is the constraint. if ( ! _.isUndefined( maxWidth ) && ! _.isUndefined( maxHeight ) ) { constraint = ( width / height > maxWidth / maxHeight ) ? 'width' : 'height'; } else if ( _.isUndefined( maxHeight ) ) { constraint = 'width'; } else if ( _.isUndefined( maxWidth ) && height > maxHeight ) { constraint = 'height'; } // If the value of the constrained side is larger than the max, // then scale the values. Otherwise return the originals; they fit. if ( 'width' === constraint && width > maxWidth ) { return { width : maxWidth, height: Math.round( maxWidth * height / width ) }; } else if ( 'height' === constraint && height > maxHeight ) { return { width : Math.round( maxHeight * width / height ), height: maxHeight }; } else { return { width : width, height: height }; } }, /** * Truncates a string by injecting an ellipsis into the middle. * Useful for filenames. * * @param {String} string * @param {Number} [length=30] * @param {String} [replacement=…] * @returns {String} The string, unless length is greater than string.length. */ truncate: function( string, length, replacement ) { length = length || 30; replacement = replacement || '…'; if ( string.length <= length ) { return string; } return string.substr( 0, length / 2 ) + replacement + string.substr( -1 * length / 2 ); } }); /** * ======================================================================== * MODELS * ======================================================================== */ /** * wp.media.attachment * * @static * @param {String} id A string used to identify a model. * @returns {wp.media.model.Attachment} */ media.attachment = function( id ) { return Attachment.get( id ); }; /** * A collection of all attachments that have been fetched from the server. * * @static * @member {wp.media.model.Attachments} */ Attachments.all = new Attachments(); /** * wp.media.query * * Shorthand for creating a new Attachments Query. * * @param {object} [props] * @returns {wp.media.model.Attachments} */ media.query = function( props ) { return new Attachments( null, { props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } ) }); }; // Clean up. Prevents mobile browsers caching $(window).on('unload', function(){ window.wp = null; }); /***/ }), /***/ 24: /***/ (function(module, exports) { var $ = Backbone.$, Attachment; /** * wp.media.model.Attachment * * @memberOf wp.media.model * * @class * @augments Backbone.Model */ Attachment = Backbone.Model.extend(/** @lends wp.media.model.Attachment.prototype */{ /** * Triggered when attachment details change * Overrides Backbone.Model.sync * * @param {string} method * @param {wp.media.model.Attachment} model * @param {Object} [options={}] * * @returns {Promise} */ sync: function( method, model, options ) { // If the attachment does not yet have an `id`, return an instantly // rejected promise. Otherwise, all of our requests will fail. if ( _.isUndefined( this.id ) ) { return $.Deferred().rejectWith( this ).promise(); } // Overload the `read` request so Attachment.fetch() functions correctly. if ( 'read' === method ) { options = options || {}; options.context = this; options.data = _.extend( options.data || {}, { action: 'get-attachment', id: this.id }); return wp.media.ajax( options ); // Overload the `update` request so properties can be saved. } else if ( 'update' === method ) { // If we do not have the necessary nonce, fail immeditately. if ( ! this.get('nonces') || ! this.get('nonces').update ) { return $.Deferred().rejectWith( this ).promise(); } options = options || {}; options.context = this; // Set the action and ID. options.data = _.extend( options.data || {}, { action: 'save-attachment', id: this.id, nonce: this.get('nonces').update, post_id: wp.media.model.settings.post.id }); // Record the values of the changed attributes. if ( model.hasChanged() ) { options.data.changes = {}; _.each( model.changed, function( value, key ) { options.data.changes[ key ] = this.get( key ); }, this ); } return wp.media.ajax( options ); // Overload the `delete` request so attachments can be removed. // This will permanently delete an attachment. } else if ( 'delete' === method ) { options = options || {}; if ( ! options.wait ) { this.destroyed = true; } options.context = this; options.data = _.extend( options.data || {}, { action: 'delete-post', id: this.id, _wpnonce: this.get('nonces')['delete'] }); return wp.media.ajax( options ).done( function() { this.destroyed = true; }).fail( function() { this.destroyed = false; }); // Otherwise, fall back to `Backbone.sync()`. } else { /** * Call `sync` directly on Backbone.Model */ return Backbone.Model.prototype.sync.apply( this, arguments ); } }, /** * Convert date strings into Date objects. * * @param {Object} resp The raw response object, typically returned by fetch() * @returns {Object} The modified response object, which is the attributes hash * to be set on the model. */ parse: function( resp ) { if ( ! resp ) { return resp; } resp.date = new Date( resp.date ); resp.modified = new Date( resp.modified ); return resp; }, /** * @param {Object} data The properties to be saved. * @param {Object} options Sync options. e.g. patch, wait, success, error. * * @this Backbone.Model * * @returns {Promise} */ saveCompat: function( data, options ) { var model = this; // If we do not have the necessary nonce, fail immeditately. if ( ! this.get('nonces') || ! this.get('nonces').update ) { return $.Deferred().rejectWith( this ).promise(); } return wp.media.post( 'save-attachment-compat', _.defaults({ id: this.id, nonce: this.get('nonces').update, post_id: wp.media.model.settings.post.id }, data ) ).done( function( resp, status, xhr ) { model.set( model.parse( resp, xhr ), options ); }); } },/** @lends wp.media.model.Attachment */{ /** * Create a new model on the static 'all' attachments collection and return it. * * @static * * @param {Object} attrs * @returns {wp.media.model.Attachment} */ create: function( attrs ) { var Attachments = wp.media.model.Attachments; return Attachments.all.push( attrs ); }, /** * Create a new model on the static 'all' attachments collection and return it. * * If this function has already been called for the id, * it returns the specified attachment. * * @static * @param {string} id A string used to identify a model. * @param {Backbone.Model|undefined} attachment * @returns {wp.media.model.Attachment} */ get: _.memoize( function( id, attachment ) { var Attachments = wp.media.model.Attachments; return Attachments.all.push( attachment || { id: id } ); }) }); module.exports = Attachment; /***/ }), /***/ 25: /***/ (function(module, exports) { /** * wp.media.model.Attachments * * A collection of attachments. * * This collection has no persistence with the server without supplying * 'options.props.query = true', which will mirror the collection * to an Attachments Query collection - @see wp.media.model.Attachments.mirror(). * * @memberOf wp.media.model * * @class * @augments Backbone.Collection * * @param {array} [models] Models to initialize with the collection. * @param {object} [options] Options hash for the collection. * @param {string} [options.props] Options hash for the initial query properties. * @param {string} [options.props.order] Initial order (ASC or DESC) for the collection. * @param {string} [options.props.orderby] Initial attribute key to order the collection by. * @param {string} [options.props.query] Whether the collection is linked to an attachments query. * @param {string} [options.observe] * @param {string} [options.filters] * */ var Attachments = Backbone.Collection.extend(/** @lends wp.media.model.Attachments.prototype */{ /** * @type {wp.media.model.Attachment} */ model: wp.media.model.Attachment, /** * @param {Array} [models=[]] Array of models used to populate the collection. * @param {Object} [options={}] */ initialize: function( models, options ) { options = options || {}; this.props = new Backbone.Model(); this.filters = options.filters || {}; // Bind default `change` events to the `props` model. this.props.on( 'change', this._changeFilteredProps, this ); this.props.on( 'change:order', this._changeOrder, this ); this.props.on( 'change:orderby', this._changeOrderby, this ); this.props.on( 'change:query', this._changeQuery, this ); this.props.set( _.defaults( options.props || {} ) ); if ( options.observe ) { this.observe( options.observe ); } }, /** * Sort the collection when the order attribute changes. * * @access private */ _changeOrder: function() { if ( this.comparator ) { this.sort(); } }, /** * Set the default comparator only when the `orderby` property is set. * * @access private * * @param {Backbone.Model} model * @param {string} orderby */ _changeOrderby: function( model, orderby ) { // If a different comparator is defined, bail. if ( this.comparator && this.comparator !== Attachments.comparator ) { return; } if ( orderby && 'post__in' !== orderby ) { this.comparator = Attachments.comparator; } else { delete this.comparator; } }, /** * If the `query` property is set to true, query the server using * the `props` values, and sync the results to this collection. * * @access private * * @param {Backbone.Model} model * @param {Boolean} query */ _changeQuery: function( model, query ) { if ( query ) { this.props.on( 'change', this._requery, this ); this._requery(); } else { this.props.off( 'change', this._requery, this ); } }, /** * @access private * * @param {Backbone.Model} model */ _changeFilteredProps: function( model ) { // If this is a query, updating the collection will be handled by // `this._requery()`. if ( this.props.get('query') ) { return; } var changed = _.chain( model.changed ).map( function( t, prop ) { var filter = Attachments.filters[ prop ], term = model.get( prop ); if ( ! filter ) { return; } if ( term && ! this.filters[ prop ] ) { this.filters[ prop ] = filter; } else if ( ! term && this.filters[ prop ] === filter ) { delete this.filters[ prop ]; } else { return; } // Record the change. return true; }, this ).any().value(); if ( ! changed ) { return; } // If no `Attachments` model is provided to source the searches // from, then automatically generate a source from the existing // models. if ( ! this._source ) { this._source = new Attachments( this.models ); } this.reset( this._source.filter( this.validator, this ) ); }, validateDestroyed: false, /** * Checks whether an attachment is valid. * * @param {wp.media.model.Attachment} attachment * @returns {Boolean} */ validator: function( attachment ) { // Filter out contextually created attachments (e.g. headers, logos, etc.). if ( ! _.isUndefined( attachment.attributes.context ) && '' !== attachment.attributes.context ) { return false; } if ( ! this.validateDestroyed && attachment.destroyed ) { return false; } return _.all( this.filters, function( filter ) { return !! filter.call( this, attachment ); }, this ); }, /** * Add or remove an attachment to the collection depending on its validity. * * @param {wp.media.model.Attachment} attachment * @param {Object} options * @returns {wp.media.model.Attachments} Returns itself to allow chaining */ validate: function( attachment, options ) { var valid = this.validator( attachment ), hasAttachment = !! this.get( attachment.cid ); if ( ! valid && hasAttachment ) { this.remove( attachment, options ); } else if ( valid && ! hasAttachment ) { this.add( attachment, options ); } return this; }, /** * Add or remove all attachments from another collection depending on each one's validity. * * @param {wp.media.model.Attachments} attachments * @param {object} [options={}] * * @fires wp.media.model.Attachments#reset * * @returns {wp.media.model.Attachments} Returns itself to allow chaining */ validateAll: function( attachments, options ) { options = options || {}; _.each( attachments.models, function( attachment ) { this.validate( attachment, { silent: true }); }, this ); if ( ! options.silent ) { this.trigger( 'reset', this, options ); } return this; }, /** * Start observing another attachments collection change events * and replicate them on this collection. * * @param {wp.media.model.Attachments} The attachments collection to observe. * @returns {wp.media.model.Attachments} Returns itself to allow chaining. */ observe: function( attachments ) { this.observers = this.observers || []; this.observers.push( attachments ); attachments.on( 'add change remove', this._validateHandler, this ); attachments.on( 'reset', this._validateAllHandler, this ); this.validateAll( attachments ); return this; }, /** * Stop replicating collection change events from another attachments collection. * * @param {wp.media.model.Attachments} The attachments collection to stop observing. * @returns {wp.media.model.Attachments} Returns itself to allow chaining */ unobserve: function( attachments ) { if ( attachments ) { attachments.off( null, null, this ); this.observers = _.without( this.observers, attachments ); } else { _.each( this.observers, function( attachments ) { attachments.off( null, null, this ); }, this ); delete this.observers; } return this; }, /** * @access private * * @param {wp.media.model.Attachments} attachment * @param {wp.media.model.Attachments} attachments * @param {Object} options * * @returns {wp.media.model.Attachments} Returns itself to allow chaining */ _validateHandler: function( attachment, attachments, options ) { // If we're not mirroring this `attachments` collection, // only retain the `silent` option. options = attachments === this.mirroring ? options : { silent: options && options.silent }; return this.validate( attachment, options ); }, /** * @access private * * @param {wp.media.model.Attachments} attachments * @param {Object} options * @returns {wp.media.model.Attachments} Returns itself to allow chaining */ _validateAllHandler: function( attachments, options ) { return this.validateAll( attachments, options ); }, /** * Start mirroring another attachments collection, clearing out any models already * in the collection. * * @param {wp.media.model.Attachments} The attachments collection to mirror. * @returns {wp.media.model.Attachments} Returns itself to allow chaining */ mirror: function( attachments ) { if ( this.mirroring && this.mirroring === attachments ) { return this; } this.unmirror(); this.mirroring = attachments; // Clear the collection silently. A `reset` event will be fired // when `observe()` calls `validateAll()`. this.reset( [], { silent: true } ); this.observe( attachments ); // Used for the search results. this.trigger( 'attachments:received', this ); return this; }, /** * Stop mirroring another attachments collection. */ unmirror: function() { if ( ! this.mirroring ) { return; } this.unobserve( this.mirroring ); delete this.mirroring; }, /** * Retrieve more attachments from the server for the collection. * * Only works if the collection is mirroring a Query Attachments collection, * and forwards to its `more` method. This collection class doesn't have * server persistence by itself. * * @param {object} options * @returns {Promise} */ more: function( options ) { var deferred = jQuery.Deferred(), mirroring = this.mirroring, attachments = this; if ( ! mirroring || ! mirroring.more ) { return deferred.resolveWith( this ).promise(); } // If we're mirroring another collection, forward `more` to // the mirrored collection. Account for a race condition by // checking if we're still mirroring that collection when // the request resolves. mirroring.more( options ).done( function() { if ( this === attachments.mirroring ) { deferred.resolveWith( this ); } // Used for the search results. attachments.trigger( 'attachments:received', this ); }); return deferred.promise(); }, /** * Whether there are more attachments that haven't been sync'd from the server * that match the collection's query. * * Only works if the collection is mirroring a Query Attachments collection, * and forwards to its `hasMore` method. This collection class doesn't have * server persistence by itself. * * @returns {boolean} */ hasMore: function() { return this.mirroring ? this.mirroring.hasMore() : false; }, /** * A custom AJAX-response parser. * * See trac ticket #24753 * * @param {Object|Array} resp The raw response Object/Array. * @param {Object} xhr * @returns {Array} The array of model attributes to be added to the collection */ parse: function( resp, xhr ) { if ( ! _.isArray( resp ) ) { resp = [resp]; } return _.map( resp, function( attrs ) { var id, attachment, newAttributes; if ( attrs instanceof Backbone.Model ) { id = attrs.get( 'id' ); attrs = attrs.attributes; } else { id = attrs.id; } attachment = wp.media.model.Attachment.get( id ); newAttributes = attachment.parse( attrs, xhr ); if ( ! _.isEqual( attachment.attributes, newAttributes ) ) { attachment.set( newAttributes ); } return attachment; }); }, /** * If the collection is a query, create and mirror an Attachments Query collection. * * @access private */ _requery: function( refresh ) { var props; if ( this.props.get('query') ) { props = this.props.toJSON(); props.cache = ( true !== refresh ); this.mirror( wp.media.model.Query.get( props ) ); } }, /** * If this collection is sorted by `menuOrder`, recalculates and saves * the menu order to the database. * * @returns {undefined|Promise} */ saveMenuOrder: function() { if ( 'menuOrder' !== this.props.get('orderby') ) { return; } // Removes any uploading attachments, updates each attachment's // menu order, and returns an object with an { id: menuOrder } // mapping to pass to the request. var attachments = this.chain().filter( function( attachment ) { return ! _.isUndefined( attachment.id ); }).map( function( attachment, index ) { // Indices start at 1. index = index + 1; attachment.set( 'menuOrder', index ); return [ attachment.id, index ]; }).object().value(); if ( _.isEmpty( attachments ) ) { return; } return wp.media.post( 'save-attachment-order', { nonce: wp.media.model.settings.post.nonce, post_id: wp.media.model.settings.post.id, attachments: attachments }); } },/** @lends wp.media.model.Attachments */{ /** * A function to compare two attachment models in an attachments collection. * * Used as the default comparator for instances of wp.media.model.Attachments * and its subclasses. @see wp.media.model.Attachments._changeOrderby(). * * @param {Backbone.Model} a * @param {Backbone.Model} b * @param {Object} options * @returns {Number} -1 if the first model should come before the second, * 0 if they are of the same rank and * 1 if the first model should come after. */ comparator: function( a, b, options ) { var key = this.props.get('orderby'), order = this.props.get('order') || 'DESC', ac = a.cid, bc = b.cid; a = a.get( key ); b = b.get( key ); if ( 'date' === key || 'modified' === key ) { a = a || new Date(); b = b || new Date(); } // If `options.ties` is set, don't enforce the `cid` tiebreaker. if ( options && options.ties ) { ac = bc = null; } return ( 'DESC' === order ) ? wp.media.compare( a, b, ac, bc ) : wp.media.compare( b, a, bc, ac ); }, /** @namespace wp.media.model.Attachments.filters */ filters: { /** * @static * Note that this client-side searching is *not* equivalent * to our server-side searching. * * @param {wp.media.model.Attachment} attachment * * @this wp.media.model.Attachments * * @returns {Boolean} */ search: function( attachment ) { if ( ! this.props.get('search') ) { return true; } return _.any(['title','filename','description','caption','name'], function( key ) { var value = attachment.get( key ); return value && -1 !== value.search( this.props.get('search') ); }, this ); }, /** * @static * @param {wp.media.model.Attachment} attachment * * @this wp.media.model.Attachments * * @returns {Boolean} */ type: function( attachment ) { var type = this.props.get('type'), atts = attachment.toJSON(), mime, found; if ( ! type || ( _.isArray( type ) && ! type.length ) ) { return true; } mime = atts.mime || ( atts.file && atts.file.type ) || ''; if ( _.isArray( type ) ) { found = _.find( type, function (t) { return -1 !== mime.indexOf( t ); } ); } else { found = -1 !== mime.indexOf( type ); } return found; }, /** * @static * @param {wp.media.model.Attachment} attachment * * @this wp.media.model.Attachments * * @returns {Boolean} */ uploadedTo: function( attachment ) { var uploadedTo = this.props.get('uploadedTo'); if ( _.isUndefined( uploadedTo ) ) { return true; } return uploadedTo === attachment.get('uploadedTo'); }, /** * @static * @param {wp.media.model.Attachment} attachment * * @this wp.media.model.Attachments * * @returns {Boolean} */ status: function( attachment ) { var status = this.props.get('status'); if ( _.isUndefined( status ) ) { return true; } return status === attachment.get('status'); } } }); module.exports = Attachments; /***/ }), /***/ 26: /***/ (function(module, exports) { var Attachments = wp.media.model.Attachments, Query; /** * wp.media.model.Query * * A collection of attachments that match the supplied query arguments. * * Note: Do NOT change this.args after the query has been initialized. * Things will break. * * @memberOf wp.media.model * * @class * @augments wp.media.model.Attachments * @augments Backbone.Collection * * @param {array} [models] Models to initialize with the collection. * @param {object} [options] Options hash. * @param {object} [options.args] Attachments query arguments. * @param {object} [options.args.posts_per_page] */ Query = Attachments.extend(/** @lends wp.media.model.Query.prototype */{ /** * @param {array} [models=[]] Array of initial models to populate the collection. * @param {object} [options={}] */ initialize: function( models, options ) { var allowed; options = options || {}; Attachments.prototype.initialize.apply( this, arguments ); this.args = options.args; this._hasMore = true; this.created = new Date(); this.filters.order = function( attachment ) { var orderby = this.props.get('orderby'), order = this.props.get('order'); if ( ! this.comparator ) { return true; } // We want any items that can be placed before the last // item in the set. If we add any items after the last // item, then we can't guarantee the set is complete. if ( this.length ) { return 1 !== this.comparator( attachment, this.last(), { ties: true }); // Handle the case where there are no items yet and // we're sorting for recent items. In that case, we want // changes that occurred after we created the query. } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) { return attachment.get( orderby ) >= this.created; // If we're sorting by menu order and we have no items, // accept any items that have the default menu order (0). } else if ( 'ASC' === order && 'menuOrder' === orderby ) { return attachment.get( orderby ) === 0; } // Otherwise, we don't want any items yet. return false; }; // Observe the central `wp.Uploader.queue` collection to watch for // new matches for the query. // // Only observe when a limited number of query args are set. There // are no filters for other properties, so observing will result in // false positives in those queries. allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent', 'author' ]; if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) { this.observe( wp.Uploader.queue ); } }, /** * Whether there are more attachments that haven't been sync'd from the server * that match the collection's query. * * @returns {boolean} */ hasMore: function() { return this._hasMore; }, /** * Fetch more attachments from the server for the collection. * * @param {object} [options={}] * @returns {Promise} */ more: function( options ) { var query = this; // If there is already a request pending, return early with the Deferred object. if ( this._more && 'pending' === this._more.state() ) { return this._more; } if ( ! this.hasMore() ) { return jQuery.Deferred().resolveWith( this ).promise(); } options = options || {}; options.remove = false; return this._more = this.fetch( options ).done( function( resp ) { if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) { query._hasMore = false; } }); }, /** * Overrides Backbone.Collection.sync * Overrides wp.media.model.Attachments.sync * * @param {String} method * @param {Backbone.Model} model * @param {Object} [options={}] * @returns {Promise} */ sync: function( method, model, options ) { var args, fallback; // Overload the read method so Attachment.fetch() functions correctly. if ( 'read' === method ) { options = options || {}; options.context = this; options.data = _.extend( options.data || {}, { action: 'query-attachments', post_id: wp.media.model.settings.post.id }); // Clone the args so manipulation is non-destructive. args = _.clone( this.args ); // Determine which page to query. if ( -1 !== args.posts_per_page ) { args.paged = Math.round( this.length / args.posts_per_page ) + 1; } options.data.query = args; return wp.media.ajax( options ); // Otherwise, fall back to Backbone.sync() } else { /** * Call wp.media.model.Attachments.sync or Backbone.sync */ fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone; return fallback.sync.apply( this, arguments ); } } }, /** @lends wp.media.model.Query */{ /** * @readonly */ defaultProps: { orderby: 'date', order: 'DESC' }, /** * @readonly */ defaultArgs: { posts_per_page: 40 }, /** * @readonly */ orderby: { allowed: [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ], /** * A map of JavaScript orderby values to their WP_Query equivalents. * @type {Object} */ valuemap: { 'id': 'ID', 'uploadedTo': 'parent', 'menuOrder': 'menu_order ID' } }, /** * A map of JavaScript query properties to their WP_Query equivalents. * * @readonly */ propmap: { 'search': 's', 'type': 'post_mime_type', 'perPage': 'posts_per_page', 'menuOrder': 'menu_order', 'uploadedTo': 'post_parent', 'status': 'post_status', 'include': 'post__in', 'exclude': 'post__not_in', 'author': 'author' }, /** * Creates and returns an Attachments Query collection given the properties. * * Caches query objects and reuses where possible. * * @static * @method * * @param {object} [props] * @param {Object} [props.cache=true] Whether to use the query cache or not. * @param {Object} [props.order] * @param {Object} [props.orderby] * @param {Object} [props.include] * @param {Object} [props.exclude] * @param {Object} [props.s] * @param {Object} [props.post_mime_type] * @param {Object} [props.posts_per_page] * @param {Object} [props.menu_order] * @param {Object} [props.post_parent] * @param {Object} [props.post_status] * @param {Object} [props.author] * @param {Object} [options] * * @returns {wp.media.model.Query} A new Attachments Query collection. */ get: (function(){ /** * @static * @type Array */ var queries = []; /** * @returns {Query} */ return function( props, options ) { var args = {}, orderby = Query.orderby, defaults = Query.defaultProps, query, cache = !! props.cache || _.isUndefined( props.cache ); // Remove the `query` property. This isn't linked to a query, // this *is* the query. delete props.query; delete props.cache; // Fill default args. _.defaults( props, defaults ); // Normalize the order. props.order = props.order.toUpperCase(); if ( 'DESC' !== props.order && 'ASC' !== props.order ) { props.order = defaults.order.toUpperCase(); } // Ensure we have a valid orderby value. if ( ! _.contains( orderby.allowed, props.orderby ) ) { props.orderby = defaults.orderby; } _.each( [ 'include', 'exclude' ], function( prop ) { if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) { props[ prop ] = [ props[ prop ] ]; } } ); // Generate the query `args` object. // Correct any differing property names. _.each( props, function( value, prop ) { if ( _.isNull( value ) ) { return; } args[ Query.propmap[ prop ] || prop ] = value; }); // Fill any other default query args. _.defaults( args, Query.defaultArgs ); // `props.orderby` does not always map directly to `args.orderby`. // Substitute exceptions specified in orderby.keymap. args.orderby = orderby.valuemap[ props.orderby ] || props.orderby; // Search the query cache for a matching query. if ( cache ) { query = _.find( queries, function( query ) { return _.isEqual( query.args, args ); }); } else { queries = []; } // Otherwise, create a new query and add it to the cache. if ( ! query ) { query = new Query( [], _.extend( options || {}, { props: props, args: args } ) ); queries.push( query ); } return query; }; }()) }); module.exports = Query; /***/ }), /***/ 27: /***/ (function(module, exports) { /** * wp.media.model.PostImage * * An instance of an image that's been embedded into a post. * * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails. * * @memberOf wp.media.model * * @class * @augments Backbone.Model * * @param {int} [attributes] Initial model attributes. * @param {int} [attributes.attachment_id] ID of the attachment. **/ var PostImage = Backbone.Model.extend(/** @lends wp.media.model.PostImage.prototype */{ initialize: function( attributes ) { var Attachment = wp.media.model.Attachment; this.attachment = false; if ( attributes.attachment_id ) { this.attachment = Attachment.get( attributes.attachment_id ); if ( this.attachment.get( 'url' ) ) { this.dfd = jQuery.Deferred(); this.dfd.resolve(); } else { this.dfd = this.attachment.fetch(); } this.bindAttachmentListeners(); } // keep url in sync with changes to the type of link this.on( 'change:link', this.updateLinkUrl, this ); this.on( 'change:size', this.updateSize, this ); this.setLinkTypeFromUrl(); this.setAspectRatio(); this.set( 'originalUrl', attributes.url ); }, bindAttachmentListeners: function() { this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl ); this.listenTo( this.attachment, 'sync', this.setAspectRatio ); this.listenTo( this.attachment, 'change', this.updateSize ); }, changeAttachment: function( attachment, props ) { this.stopListening( this.attachment ); this.attachment = attachment; this.bindAttachmentListeners(); this.set( 'attachment_id', this.attachment.get( 'id' ) ); this.set( 'caption', this.attachment.get( 'caption' ) ); this.set( 'alt', this.attachment.get( 'alt' ) ); this.set( 'size', props.get( 'size' ) ); this.set( 'align', props.get( 'align' ) ); this.set( 'link', props.get( 'link' ) ); this.updateLinkUrl(); this.updateSize(); }, setLinkTypeFromUrl: function() { var linkUrl = this.get( 'linkUrl' ), type; if ( ! linkUrl ) { this.set( 'link', 'none' ); return; } // default to custom if there is a linkUrl type = 'custom'; if ( this.attachment ) { if ( this.attachment.get( 'url' ) === linkUrl ) { type = 'file'; } else if ( this.attachment.get( 'link' ) === linkUrl ) { type = 'post'; } } else { if ( this.get( 'url' ) === linkUrl ) { type = 'file'; } } this.set( 'link', type ); }, updateLinkUrl: function() { var link = this.get( 'link' ), url; switch( link ) { case 'file': if ( this.attachment ) { url = this.attachment.get( 'url' ); } else { url = this.get( 'url' ); } this.set( 'linkUrl', url ); break; case 'post': this.set( 'linkUrl', this.attachment.get( 'link' ) ); break; case 'none': this.set( 'linkUrl', '' ); break; } }, updateSize: function() { var size; if ( ! this.attachment ) { return; } if ( this.get( 'size' ) === 'custom' ) { this.set( 'width', this.get( 'customWidth' ) ); this.set( 'height', this.get( 'customHeight' ) ); this.set( 'url', this.get( 'originalUrl' ) ); return; } size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ]; if ( ! size ) { return; } this.set( 'url', size.url ); this.set( 'width', size.width ); this.set( 'height', size.height ); }, setAspectRatio: function() { var full; if ( this.attachment && this.attachment.get( 'sizes' ) ) { full = this.attachment.get( 'sizes' ).full; if ( full ) { this.set( 'aspectRatio', full.width / full.height ); return; } } this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) ); } }); module.exports = PostImage; /***/ }), /***/ 28: /***/ (function(module, exports) { var Attachments = wp.media.model.Attachments, Selection; /** * wp.media.model.Selection * * A selection of attachments. * * @memberOf wp.media.model * * @class * @augments wp.media.model.Attachments * @augments Backbone.Collection */ Selection = Attachments.extend(/** @lends wp.media.model.Selection.prototype */{ /** * Refresh the `single` model whenever the selection changes. * Binds `single` instead of using the context argument to ensure * it receives no parameters. * * @param {Array} [models=[]] Array of models used to populate the collection. * @param {Object} [options={}] */ initialize: function( models, options ) { /** * call 'initialize' directly on the parent class */ Attachments.prototype.initialize.apply( this, arguments ); this.multiple = options && options.multiple; this.on( 'add remove reset', _.bind( this.single, this, false ) ); }, /** * If the workflow does not support multi-select, clear out the selection * before adding a new attachment to it. * * @param {Array} models * @param {Object} options * @returns {wp.media.model.Attachment[]} */ add: function( models, options ) { if ( ! this.multiple ) { this.remove( this.models ); } /** * call 'add' directly on the parent class */ return Attachments.prototype.add.call( this, models, options ); }, /** * Fired when toggling (clicking on) an attachment in the modal. * * @param {undefined|boolean|wp.media.model.Attachment} model * * @fires wp.media.model.Selection#selection:single * @fires wp.media.model.Selection#selection:unsingle * * @returns {Backbone.Model} */ single: function( model ) { var previous = this._single; // If a `model` is provided, use it as the single model. if ( model ) { this._single = model; } // If the single model isn't in the selection, remove it. if ( this._single && ! this.get( this._single.cid ) ) { delete this._single; } this._single = this._single || this.last(); // If single has changed, fire an event. if ( this._single !== previous ) { if ( previous ) { previous.trigger( 'selection:unsingle', previous, this ); // If the model was already removed, trigger the collection // event manually. if ( ! this.get( previous.cid ) ) { this.trigger( 'selection:unsingle', previous, this ); } } if ( this._single ) { this._single.trigger( 'selection:single', this._single, this ); } } // Return the single model, or the last model as a fallback. return this._single; } }); module.exports = Selection; /***/ }) /******/ });; ;var MXI_DEBUG = false; /** * mOxie - multi-runtime File API & XMLHttpRequest L2 Polyfill * v1.3.5 * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing * * Date: 2016-05-15 */ /** * Compiled inline version. (Library mode) */ /** * Modified for WordPress, Silverlight and Flash runtimes support was removed. * See https://core.trac.wordpress.org/ticket/41755. */ /*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */ /*globals $code */ (function(exports, undefined) { "use strict"; var modules = {}; function require(ids, callback) { var module, defs = []; for (var i = 0; i < ids.length; ++i) { module = modules[ids[i]] || resolve(ids[i]); if (!module) { throw 'module definition dependecy not found: ' + ids[i]; } defs.push(module); } callback.apply(null, defs); } function define(id, dependencies, definition) { if (typeof id !== 'string') { throw 'invalid module definition, module id must be defined and be a string'; } if (dependencies === undefined) { throw 'invalid module definition, dependencies must be specified'; } if (definition === undefined) { throw 'invalid module definition, definition function must be specified'; } require(dependencies, function() { modules[id] = definition.apply(null, arguments); }); } function defined(id) { return !!modules[id]; } function resolve(id) { var target = exports; var fragments = id.split(/[.\/]/); for (var fi = 0; fi < fragments.length; ++fi) { if (!target[fragments[fi]]) { return; } target = target[fragments[fi]]; } return target; } function expose(ids) { for (var i = 0; i < ids.length; i++) { var target = exports; var id = ids[i]; var fragments = id.split(/[.\/]/); for (var fi = 0; fi < fragments.length - 1; ++fi) { if (target[fragments[fi]] === undefined) { target[fragments[fi]] = {}; } target = target[fragments[fi]]; } target[fragments[fragments.length - 1]] = modules[id]; } } // Included from: src/javascript/core/utils/Basic.js /** * Basic.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/core/utils/Basic', [], function() { /** Gets the true type of the built-in object (better version of typeof). @author Angus Croll (http://javascriptweblog.wordpress.com/) @method typeOf @for Utils @static @param {Object} o Object to check. @return {String} Object [[Class]] */ var typeOf = function(o) { var undef; if (o === undef) { return 'undefined'; } else if (o === null) { return 'null'; } else if (o.nodeType) { return 'node'; } // the snippet below is awesome, however it fails to detect null, undefined and arguments types in IE lte 8 return ({}).toString.call(o).match(/\s([a-z|A-Z]+)/)[1].toLowerCase(); }; /** Extends the specified object with another object. @method extend @static @param {Object} target Object to extend. @param {Object} [obj]* Multiple objects to extend with. @return {Object} Same as target, the extended object. */ var extend = function(target) { var undef; each(arguments, function(arg, i) { if (i > 0) { each(arg, function(value, key) { if (value !== undef) { if (typeOf(target[key]) === typeOf(value) && !!~inArray(typeOf(value), ['array', 'object'])) { extend(target[key], value); } else { target[key] = value; } } }); } }); return target; }; /** Executes the callback function for each item in array/object. If you return false in the callback it will break the loop. @method each @static @param {Object} obj Object to iterate. @param {function} callback Callback function to execute for each item. */ var each = function(obj, callback) { var length, key, i, undef; if (obj) { if (typeOf(obj.length) === 'number') { // it might be Array, FileList or even arguments object // Loop array items for (i = 0, length = obj.length; i < length; i++) { if (callback(obj[i], i) === false) { return; } } } else if (typeOf(obj) === 'object') { // Loop object items for (key in obj) { if (obj.hasOwnProperty(key)) { if (callback(obj[key], key) === false) { return; } } } } } }; /** Checks if object is empty. @method isEmptyObj @static @param {Object} o Object to check. @return {Boolean} */ var isEmptyObj = function(obj) { var prop; if (!obj || typeOf(obj) !== 'object') { return true; } for (prop in obj) { return false; } return true; }; /** Recieve an array of functions (usually async) to call in sequence, each function receives a callback as first argument that it should call, when it completes. Finally, after everything is complete, main callback is called. Passing truthy value to the callback as a first argument will interrupt the sequence and invoke main callback immediately. @method inSeries @static @param {Array} queue Array of functions to call in sequence @param {Function} cb Main callback that is called in the end, or in case of error */ var inSeries = function(queue, cb) { var i = 0, length = queue.length; if (typeOf(cb) !== 'function') { cb = function() {}; } if (!queue || !queue.length) { cb(); } function callNext(i) { if (typeOf(queue[i]) === 'function') { queue[i](function(error) { /*jshint expr:true */ ++i < length && !error ? callNext(i) : cb(error); }); } } callNext(i); }; /** Recieve an array of functions (usually async) to call in parallel, each function receives a callback as first argument that it should call, when it completes. After everything is complete, main callback is called. Passing truthy value to the callback as a first argument will interrupt the process and invoke main callback immediately. @method inParallel @static @param {Array} queue Array of functions to call in sequence @param {Function} cb Main callback that is called in the end, or in case of error */ var inParallel = function(queue, cb) { var count = 0, num = queue.length, cbArgs = new Array(num); each(queue, function(fn, i) { fn(function(error) { if (error) { return cb(error); } var args = [].slice.call(arguments); args.shift(); // strip error - undefined or not cbArgs[i] = args; count++; if (count === num) { cbArgs.unshift(null); cb.apply(this, cbArgs); } }); }); }; /** Find an element in array and return it's index if present, otherwise return -1. @method inArray @static @param {Mixed} needle Element to find @param {Array} array @return {Int} Index of the element, or -1 if not found */ var inArray = function(needle, array) { if (array) { if (Array.prototype.indexOf) { return Array.prototype.indexOf.call(array, needle); } for (var i = 0, length = array.length; i < length; i++) { if (array[i] === needle) { return i; } } } return -1; }; /** Returns elements of first array if they are not present in second. And false - otherwise. @private @method arrayDiff @param {Array} needles @param {Array} array @return {Array|Boolean} */ var arrayDiff = function(needles, array) { var diff = []; if (typeOf(needles) !== 'array') { needles = [needles]; } if (typeOf(array) !== 'array') { array = [array]; } for (var i in needles) { if (inArray(needles[i], array) === -1) { diff.push(needles[i]); } } return diff.length ? diff : false; }; /** Find intersection of two arrays. @private @method arrayIntersect @param {Array} array1 @param {Array} array2 @return {Array} Intersection of two arrays or null if there is none */ var arrayIntersect = function(array1, array2) { var result = []; each(array1, function(item) { if (inArray(item, array2) !== -1) { result.push(item); } }); return result.length ? result : null; }; /** Forces anything into an array. @method toArray @static @param {Object} obj Object with length field. @return {Array} Array object containing all items. */ var toArray = function(obj) { var i, arr = []; for (i = 0; i < obj.length; i++) { arr[i] = obj[i]; } return arr; }; /** Generates an unique ID. The only way a user would be able to get the same ID is if the two persons at the same exact millisecond manage to get the same 5 random numbers between 0-65535; it also uses a counter so each ID is guaranteed to be unique for the given page. It is more probable for the earth to be hit with an asteroid. @method guid @static @param {String} prefix to prepend (by default 'o' will be prepended). @method guid @return {String} Virtually unique id. */ var guid = (function() { var counter = 0; return function(prefix) { var guid = new Date().getTime().toString(32), i; for (i = 0; i < 5; i++) { guid += Math.floor(Math.random() * 65535).toString(32); } return (prefix || 'o_') + guid + (counter++).toString(32); }; }()); /** Trims white spaces around the string @method trim @static @param {String} str @return {String} */ var trim = function(str) { if (!str) { return str; } return String.prototype.trim ? String.prototype.trim.call(str) : str.toString().replace(/^\s*/, '').replace(/\s*$/, ''); }; /** Parses the specified size string into a byte value. For example 10kb becomes 10240. @method parseSizeStr @static @param {String/Number} size String to parse or number to just pass through. @return {Number} Size in bytes. */ var parseSizeStr = function(size) { if (typeof(size) !== 'string') { return size; } var muls = { t: 1099511627776, g: 1073741824, m: 1048576, k: 1024 }, mul; size = /^([0-9\.]+)([tmgk]?)$/.exec(size.toLowerCase().replace(/[^0-9\.tmkg]/g, '')); mul = size[2]; size = +size[1]; if (muls.hasOwnProperty(mul)) { size *= muls[mul]; } return Math.floor(size); }; /** * Pseudo sprintf implementation - simple way to replace tokens with specified values. * * @param {String} str String with tokens * @return {String} String with replaced tokens */ var sprintf = function(str) { var args = [].slice.call(arguments, 1); return str.replace(/%[a-z]/g, function() { var value = args.shift(); return typeOf(value) !== 'undefined' ? value : ''; }); }; return { guid: guid, typeOf: typeOf, extend: extend, each: each, isEmptyObj: isEmptyObj, inSeries: inSeries, inParallel: inParallel, inArray: inArray, arrayDiff: arrayDiff, arrayIntersect: arrayIntersect, toArray: toArray, trim: trim, sprintf: sprintf, parseSizeStr: parseSizeStr }; }); // Included from: src/javascript/core/utils/Env.js /** * Env.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define("moxie/core/utils/Env", [ "moxie/core/utils/Basic" ], function(Basic) { /** * UAParser.js v0.7.7 * Lightweight JavaScript-based User-Agent string parser * https://github.com/faisalman/ua-parser-js * * Copyright © 2012-2015 Faisal Salman * Dual licensed under GPLv2 & MIT */ var UAParser = (function (undefined) { ////////////// // Constants ///////////// var EMPTY = '', UNKNOWN = '?', FUNC_TYPE = 'function', UNDEF_TYPE = 'undefined', OBJ_TYPE = 'object', MAJOR = 'major', MODEL = 'model', NAME = 'name', TYPE = 'type', VENDOR = 'vendor', VERSION = 'version', ARCHITECTURE= 'architecture', CONSOLE = 'console', MOBILE = 'mobile', TABLET = 'tablet'; /////////// // Helper ////////// var util = { has : function (str1, str2) { return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1; }, lowerize : function (str) { return str.toLowerCase(); } }; /////////////// // Map helper ////////////// var mapper = { rgx : function () { // loop through all regexes maps for (var result, i = 0, j, k, p, q, matches, match, args = arguments; i < args.length; i += 2) { var regex = args[i], // even sequence (0,2,4,..) props = args[i + 1]; // odd sequence (1,3,5,..) // construct object barebones if (typeof(result) === UNDEF_TYPE) { result = {}; for (p in props) { q = props[p]; if (typeof(q) === OBJ_TYPE) { result[q[0]] = undefined; } else { result[q] = undefined; } } } // try matching uastring with regexes for (j = k = 0; j < regex.length; j++) { matches = regex[j].exec(this.getUA()); if (!!matches) { for (p = 0; p < props.length; p++) { match = matches[++k]; q = props[p]; // check if given property is actually array if (typeof(q) === OBJ_TYPE && q.length > 0) { if (q.length == 2) { if (typeof(q[1]) == FUNC_TYPE) { // assign modified match result[q[0]] = q[1].call(this, match); } else { // assign given value, ignore regex match result[q[0]] = q[1]; } } else if (q.length == 3) { // check whether function or regex if (typeof(q[1]) === FUNC_TYPE && !(q[1].exec && q[1].test)) { // call function (usually string mapper) result[q[0]] = match ? q[1].call(this, match, q[2]) : undefined; } else { // sanitize match using given regex result[q[0]] = match ? match.replace(q[1], q[2]) : undefined; } } else if (q.length == 4) { result[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined; } } else { result[q] = match ? match : undefined; } } break; } } if(!!matches) break; // break the loop immediately if match found } return result; }, str : function (str, map) { for (var i in map) { // check if array if (typeof(map[i]) === OBJ_TYPE && map[i].length > 0) { for (var j = 0; j < map[i].length; j++) { if (util.has(map[i][j], str)) { return (i === UNKNOWN) ? undefined : i; } } } else if (util.has(map[i], str)) { return (i === UNKNOWN) ? undefined : i; } } return str; } }; /////////////// // String map ////////////// var maps = { browser : { oldsafari : { major : { '1' : ['/8', '/1', '/3'], '2' : '/4', '?' : '/' }, version : { '1.0' : '/8', '1.2' : '/1', '1.3' : '/3', '2.0' : '/412', '2.0.2' : '/416', '2.0.3' : '/417', '2.0.4' : '/419', '?' : '/' } } }, device : { sprint : { model : { 'Evo Shift 4G' : '7373KT' }, vendor : { 'HTC' : 'APA', 'Sprint' : 'Sprint' } } }, os : { windows : { version : { 'ME' : '4.90', 'NT 3.11' : 'NT3.51', 'NT 4.0' : 'NT4.0', '2000' : 'NT 5.0', 'XP' : ['NT 5.1', 'NT 5.2'], 'Vista' : 'NT 6.0', '7' : 'NT 6.1', '8' : 'NT 6.2', '8.1' : 'NT 6.3', 'RT' : 'ARM' } } } }; ////////////// // Regex map ///////////// var regexes = { browser : [[ // Presto based /(opera\smini)\/([\w\.-]+)/i, // Opera Mini /(opera\s[mobiletab]+).+version\/([\w\.-]+)/i, // Opera Mobi/Tablet /(opera).+version\/([\w\.]+)/i, // Opera > 9.80 /(opera)[\/\s]+([\w\.]+)/i // Opera < 9.80 ], [NAME, VERSION], [ /\s(opr)\/([\w\.]+)/i // Opera Webkit ], [[NAME, 'Opera'], VERSION], [ // Mixed /(kindle)\/([\w\.]+)/i, // Kindle /(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]+)*/i, // Lunascape/Maxthon/Netfront/Jasmine/Blazer // Trident based /(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?([\w\.]*)/i, // Avant/IEMobile/SlimBrowser/Baidu /(?:ms|\()(ie)\s([\w\.]+)/i, // Internet Explorer // Webkit/KHTML based /(rekonq)\/([\w\.]+)*/i, // Rekonq /(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi)\/([\w\.-]+)/i // Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron ], [NAME, VERSION], [ /(trident).+rv[:\s]([\w\.]+).+like\sgecko/i // IE11 ], [[NAME, 'IE'], VERSION], [ /(edge)\/((\d+)?[\w\.]+)/i // Microsoft Edge ], [NAME, VERSION], [ /(yabrowser)\/([\w\.]+)/i // Yandex ], [[NAME, 'Yandex'], VERSION], [ /(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon ], [[NAME, /_/g, ' '], VERSION], [ /(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i, // Chrome/OmniWeb/Arora/Tizen/Nokia /(uc\s?browser|qqbrowser)[\/\s]?([\w\.]+)/i // UCBrowser/QQBrowser ], [NAME, VERSION], [ /(dolfin)\/([\w\.]+)/i // Dolphin ], [[NAME, 'Dolphin'], VERSION], [ /((?:android.+)crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS ], [[NAME, 'Chrome'], VERSION], [ /XiaoMi\/MiuiBrowser\/([\w\.]+)/i // MIUI Browser ], [VERSION, [NAME, 'MIUI Browser']], [ /android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)/i // Android Browser ], [VERSION, [NAME, 'Android Browser']], [ /FBAV\/([\w\.]+);/i // Facebook App for iOS ], [VERSION, [NAME, 'Facebook']], [ /version\/([\w\.]+).+?mobile\/\w+\s(safari)/i // Mobile Safari ], [VERSION, [NAME, 'Mobile Safari']], [ /version\/([\w\.]+).+?(mobile\s?safari|safari)/i // Safari & Safari Mobile ], [VERSION, NAME], [ /webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i // Safari < 3.0 ], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [ /(konqueror)\/([\w\.]+)/i, // Konqueror /(webkit|khtml)\/([\w\.]+)/i ], [NAME, VERSION], [ // Gecko based /(navigator|netscape)\/([\w\.-]+)/i // Netscape ], [[NAME, 'Netscape'], VERSION], [ /(swiftfox)/i, // Swiftfox /(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i, // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror /(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/([\w\.-]+)/i, // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix /(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i, // Mozilla // Other /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf)[\/\s]?([\w\.]+)/i, // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf /(links)\s\(([\w\.]+)/i, // Links /(gobrowser)\/?([\w\.]+)*/i, // GoBrowser /(ice\s?browser)\/v?([\w\._]+)/i, // ICE Browser /(mosaic)[\/\s]([\w\.]+)/i // Mosaic ], [NAME, VERSION] ], engine : [[ /windows.+\sedge\/([\w\.]+)/i // EdgeHTML ], [VERSION, [NAME, 'EdgeHTML']], [ /(presto)\/([\w\.]+)/i, // Presto /(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m /(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i, // KHTML/Tasman/Links /(icab)[\/\s]([23]\.[\d\.]+)/i // iCab ], [NAME, VERSION], [ /rv\:([\w\.]+).*(gecko)/i // Gecko ], [VERSION, NAME] ], os : [[ // Windows based /microsoft\s(windows)\s(vista|xp)/i // Windows (iTunes) ], [NAME, VERSION], [ /(windows)\snt\s6\.2;\s(arm)/i, // Windows RT /(windows\sphone(?:\sos)*|windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i ], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [ /(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i ], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [ // Mobile/Embedded OS /\((bb)(10);/i // BlackBerry 10 ], [[NAME, 'BlackBerry'], VERSION], [ /(blackberry)\w*\/?([\w\.]+)*/i, // Blackberry /(tizen)[\/\s]([\w\.]+)/i, // Tizen /(android|webos|palm\os|qnx|bada|rim\stablet\sos|meego|contiki)[\/\s-]?([\w\.]+)*/i, // Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki /linux;.+(sailfish);/i // Sailfish OS ], [NAME, VERSION], [ /(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i // Symbian ], [[NAME, 'Symbian'], VERSION], [ /\((series40);/i // Series 40 ], [NAME], [ /mozilla.+\(mobile;.+gecko.+firefox/i // Firefox OS ], [[NAME, 'Firefox OS'], VERSION], [ // Console /(nintendo|playstation)\s([wids3portablevu]+)/i, // Nintendo/Playstation // GNU/Linux based /(mint)[\/\s\(]?(\w+)*/i, // Mint /(mageia|vectorlinux)[;\s]/i, // Mageia/VectorLinux /(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?([\w\.-]+)*/i, // Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware // Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus /(hurd|linux)\s?([\w\.]+)*/i, // Hurd/Linux /(gnu)\s?([\w\.]+)*/i // GNU ], [NAME, VERSION], [ /(cros)\s[\w]+\s([\w\.]+\w)/i // Chromium OS ], [[NAME, 'Chromium OS'], VERSION],[ // Solaris /(sunos)\s?([\w\.]+\d)*/i // Solaris ], [[NAME, 'Solaris'], VERSION], [ // BSD based /\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly ], [NAME, VERSION],[ /(ip[honead]+)(?:.*os\s*([\w]+)*\slike\smac|;\sopera)/i // iOS ], [[NAME, 'iOS'], [VERSION, /_/g, '.']], [ /(mac\sos\sx)\s?([\w\s\.]+\w)*/i, /(macintosh|mac(?=_powerpc)\s)/i // Mac OS ], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [ // Other /((?:open)?solaris)[\/\s-]?([\w\.]+)*/i, // Solaris /(haiku)\s(\w+)/i, // Haiku /(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i, // AIX /(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms)/i, // Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS /(unix)\s?([\w\.]+)*/i // UNIX ], [NAME, VERSION] ] }; ///////////////// // Constructor //////////////// var UAParser = function (uastring) { var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY); this.getBrowser = function () { return mapper.rgx.apply(this, regexes.browser); }; this.getEngine = function () { return mapper.rgx.apply(this, regexes.engine); }; this.getOS = function () { return mapper.rgx.apply(this, regexes.os); }; this.getResult = function() { return { ua : this.getUA(), browser : this.getBrowser(), engine : this.getEngine(), os : this.getOS() }; }; this.getUA = function () { return ua; }; this.setUA = function (uastring) { ua = uastring; return this; }; this.setUA(ua); }; return UAParser; })(); function version_compare(v1, v2, operator) { // From: http://phpjs.org/functions // + original by: Philippe Jausions (http://pear.php.net/user/jausions) // + original by: Aidan Lister (http://aidanlister.com/) // + reimplemented by: Kankrelune (http://www.webfaktory.info/) // + improved by: Brett Zamir (http://brett-zamir.me) // + improved by: Scott Baker // + improved by: Theriault // * example 1: version_compare('8.2.5rc', '8.2.5a'); // * returns 1: 1 // * example 2: version_compare('8.2.50', '8.2.52', '<'); // * returns 2: true // * example 3: version_compare('5.3.0-dev', '5.3.0'); // * returns 3: -1 // * example 4: version_compare('4.1.0.52','4.01.0.51'); // * returns 4: 1 // Important: compare must be initialized at 0. var i = 0, x = 0, compare = 0, // vm maps textual PHP versions to negatives so they're less than 0. // PHP currently defines these as CASE-SENSITIVE. It is important to // leave these as negatives so that they can come before numerical versions // and as if no letters were there to begin with. // (1alpha is < 1 and < 1.1 but > 1dev1) // If a non-numerical value can't be mapped to this table, it receives // -7 as its value. vm = { 'dev': -6, 'alpha': -5, 'a': -5, 'beta': -4, 'b': -4, 'RC': -3, 'rc': -3, '#': -2, 'p': 1, 'pl': 1 }, // This function will be called to prepare each version argument. // It replaces every _, -, and + with a dot. // It surrounds any nonsequence of numbers/dots with dots. // It replaces sequences of dots with a single dot. // version_compare('4..0', '4.0') == 0 // Important: A string of 0 length needs to be converted into a value // even less than an unexisting value in vm (-7), hence [-8]. // It's also important to not strip spaces because of this. // version_compare('', ' ') == 1 prepVersion = function (v) { v = ('' + v).replace(/[_\-+]/g, '.'); v = v.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.'); return (!v.length ? [-8] : v.split('.')); }, // This converts a version component to a number. // Empty component becomes 0. // Non-numerical component becomes a negative number. // Numerical component becomes itself as an integer. numVersion = function (v) { return !v ? 0 : (isNaN(v) ? vm[v] || -7 : parseInt(v, 10)); }; v1 = prepVersion(v1); v2 = prepVersion(v2); x = Math.max(v1.length, v2.length); for (i = 0; i < x; i++) { if (v1[i] == v2[i]) { continue; } v1[i] = numVersion(v1[i]); v2[i] = numVersion(v2[i]); if (v1[i] < v2[i]) { compare = -1; break; } else if (v1[i] > v2[i]) { compare = 1; break; } } if (!operator) { return compare; } // Important: operator is CASE-SENSITIVE. // "No operator" seems to be treated as "<." // Any other values seem to make the function return null. switch (operator) { case '>': case 'gt': return (compare > 0); case '>=': case 'ge': return (compare >= 0); case '<=': case 'le': return (compare <= 0); case '==': case '=': case 'eq': return (compare === 0); case '<>': case '!=': case 'ne': return (compare !== 0); case '': case '<': case 'lt': return (compare < 0); default: return null; } } var can = (function() { var caps = { define_property: (function() { /* // currently too much extra code required, not exactly worth it try { // as of IE8, getters/setters are supported only on DOM elements var obj = {}; if (Object.defineProperty) { Object.defineProperty(obj, 'prop', { enumerable: true, configurable: true }); return true; } } catch(ex) {} if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { return true; }*/ return false; }()), create_canvas: (function() { // On the S60 and BB Storm, getContext exists, but always returns undefined // so we actually have to call getContext() to verify // github.com/Modernizr/Modernizr/issues/issue/97/ var el = document.createElement('canvas'); return !!(el.getContext && el.getContext('2d')); }()), return_response_type: function(responseType) { try { if (Basic.inArray(responseType, ['', 'text', 'document']) !== -1) { return true; } else if (window.XMLHttpRequest) { var xhr = new XMLHttpRequest(); xhr.open('get', '/'); // otherwise Gecko throws an exception if ('responseType' in xhr) { xhr.responseType = responseType; // as of 23.0.1271.64, Chrome switched from throwing exception to merely logging it to the console (why? o why?) if (xhr.responseType !== responseType) { return false; } return true; } } } catch (ex) {} return false; }, // ideas for this heavily come from Modernizr (http://modernizr.com/) use_data_uri: (function() { var du = new Image(); du.onload = function() { caps.use_data_uri = (du.width === 1 && du.height === 1); }; setTimeout(function() { du.src = "data:image/gif;base64,R0lGODlhAQABAIAAAP8AAAAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="; }, 1); return false; }()), use_data_uri_over32kb: function() { // IE8 return caps.use_data_uri && (Env.browser !== 'IE' || Env.version >= 9); }, use_data_uri_of: function(bytes) { return (caps.use_data_uri && bytes < 33000 || caps.use_data_uri_over32kb()); }, use_fileinput: function() { if (navigator.userAgent.match(/(Android (1.0|1.1|1.5|1.6|2.0|2.1))|(Windows Phone (OS 7|8.0))|(XBLWP)|(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|(Kindle\/(1.0|2.0|2.5|3.0))/)) { return false; } var el = document.createElement('input'); el.setAttribute('type', 'file'); return !el.disabled; } }; return function(cap) { var args = [].slice.call(arguments); args.shift(); // shift of cap return Basic.typeOf(caps[cap]) === 'function' ? caps[cap].apply(this, args) : !!caps[cap]; }; }()); var uaResult = new UAParser().getResult(); var Env = { can: can, uaParser: UAParser, browser: uaResult.browser.name, version: uaResult.browser.version, os: uaResult.os.name, // everybody intuitively types it in a lowercase for some reason osVersion: uaResult.os.version, verComp: version_compare, global_event_dispatcher: "moxie.core.EventTarget.instance.dispatchEvent" }; // for backward compatibility // @deprecated Use `Env.os` instead Env.OS = Env.os; if (MXI_DEBUG) { Env.debug = { runtime: true, events: false }; Env.log = function() { function logObj(data) { // TODO: this should recursively print out the object in a pretty way console.appendChild(document.createTextNode(data + "\n")); } var data = arguments[0]; if (Basic.typeOf(data) === 'string') { data = Basic.sprintf.apply(this, arguments); } if (window && window.console && window.console.log) { window.console.log(data); } else if (document) { var console = document.getElementById('moxie-console'); if (!console) { console = document.createElement('pre'); console.id = 'moxie-console'; //console.style.display = 'none'; document.body.appendChild(console); } if (Basic.inArray(Basic.typeOf(data), ['object', 'array']) !== -1) { logObj(data); } else { console.appendChild(document.createTextNode(data + "\n")); } } }; } return Env; }); // Included from: src/javascript/core/I18n.js /** * I18n.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define("moxie/core/I18n", [ "moxie/core/utils/Basic" ], function(Basic) { var i18n = {}; return { /** * Extends the language pack object with new items. * * @param {Object} pack Language pack items to add. * @return {Object} Extended language pack object. */ addI18n: function(pack) { return Basic.extend(i18n, pack); }, /** * Translates the specified string by checking for the english string in the language pack lookup. * * @param {String} str String to look for. * @return {String} Translated string or the input string if it wasn't found. */ translate: function(str) { return i18n[str] || str; }, /** * Shortcut for translate function * * @param {String} str String to look for. * @return {String} Translated string or the input string if it wasn't found. */ _: function(str) { return this.translate(str); }, /** * Pseudo sprintf implementation - simple way to replace tokens with specified values. * * @param {String} str String with tokens * @return {String} String with replaced tokens */ sprintf: function(str) { var args = [].slice.call(arguments, 1); return str.replace(/%[a-z]/g, function() { var value = args.shift(); return Basic.typeOf(value) !== 'undefined' ? value : ''; }); } }; }); // Included from: src/javascript/core/utils/Mime.js /** * Mime.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define("moxie/core/utils/Mime", [ "moxie/core/utils/Basic", "moxie/core/I18n" ], function(Basic, I18n) { var mimeData = "" + "application/msword,doc dot," + "application/pdf,pdf," + "application/pgp-signature,pgp," + "application/postscript,ps ai eps," + "application/rtf,rtf," + "application/vnd.ms-excel,xls xlb," + "application/vnd.ms-powerpoint,ppt pps pot," + "application/zip,zip," + "application/x-shockwave-flash,swf swfl," + "application/vnd.openxmlformats-officedocument.wordprocessingml.document,docx," + "application/vnd.openxmlformats-officedocument.wordprocessingml.template,dotx," + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,xlsx," + "application/vnd.openxmlformats-officedocument.presentationml.presentation,pptx," + "application/vnd.openxmlformats-officedocument.presentationml.template,potx," + "application/vnd.openxmlformats-officedocument.presentationml.slideshow,ppsx," + "application/x-javascript,js," + "application/json,json," + "audio/mpeg,mp3 mpga mpega mp2," + "audio/x-wav,wav," + "audio/x-m4a,m4a," + "audio/ogg,oga ogg," + "audio/aiff,aiff aif," + "audio/flac,flac," + "audio/aac,aac," + "audio/ac3,ac3," + "audio/x-ms-wma,wma," + "image/bmp,bmp," + "image/gif,gif," + "image/jpeg,jpg jpeg jpe," + "image/photoshop,psd," + "image/png,png," + "image/svg+xml,svg svgz," + "image/tiff,tiff tif," + "text/plain,asc txt text diff log," + "text/html,htm html xhtml," + "text/css,css," + "text/csv,csv," + "text/rtf,rtf," + "video/mpeg,mpeg mpg mpe m2v," + "video/quicktime,qt mov," + "video/mp4,mp4," + "video/x-m4v,m4v," + "video/x-flv,flv," + "video/x-ms-wmv,wmv," + "video/avi,avi," + "video/webm,webm," + "video/3gpp,3gpp 3gp," + "video/3gpp2,3g2," + "video/vnd.rn-realvideo,rv," + "video/ogg,ogv," + "video/x-matroska,mkv," + "application/vnd.oasis.opendocument.formula-template,otf," + "application/octet-stream,exe"; var Mime = { mimes: {}, extensions: {}, // Parses the default mime types string into a mimes and extensions lookup maps addMimeType: function (mimeData) { var items = mimeData.split(/,/), i, ii, ext; for (i = 0; i < items.length; i += 2) { ext = items[i + 1].split(/ /); // extension to mime lookup for (ii = 0; ii < ext.length; ii++) { this.mimes[ext[ii]] = items[i]; } // mime to extension lookup this.extensions[items[i]] = ext; } }, extList2mimes: function (filters, addMissingExtensions) { var self = this, ext, i, ii, type, mimes = []; // convert extensions to mime types list for (i = 0; i < filters.length; i++) { ext = filters[i].extensions.split(/\s*,\s*/); for (ii = 0; ii < ext.length; ii++) { // if there's an asterisk in the list, then accept attribute is not required if (ext[ii] === '*') { return []; } type = self.mimes[ext[ii]]; if (type && Basic.inArray(type, mimes) === -1) { mimes.push(type); } // future browsers should filter by extension, finally if (addMissingExtensions && /^\w+$/.test(ext[ii])) { mimes.push('.' + ext[ii]); } else if (!type) { // if we have no type in our map, then accept all return []; } } } return mimes; }, mimes2exts: function(mimes) { var self = this, exts = []; Basic.each(mimes, function(mime) { if (mime === '*') { exts = []; return false; } // check if this thing looks like mime type var m = mime.match(/^(\w+)\/(\*|\w+)$/); if (m) { if (m[2] === '*') { // wildcard mime type detected Basic.each(self.extensions, function(arr, mime) { if ((new RegExp('^' + m[1] + '/')).test(mime)) { [].push.apply(exts, self.extensions[mime]); } }); } else if (self.extensions[mime]) { [].push.apply(exts, self.extensions[mime]); } } }); return exts; }, mimes2extList: function(mimes) { var accept = [], exts = []; if (Basic.typeOf(mimes) === 'string') { mimes = Basic.trim(mimes).split(/\s*,\s*/); } exts = this.mimes2exts(mimes); accept.push({ title: I18n.translate('Files'), extensions: exts.length ? exts.join(',') : '*' }); // save original mimes string accept.mimes = mimes; return accept; }, getFileExtension: function(fileName) { var matches = fileName && fileName.match(/\.([^.]+)$/); if (matches) { return matches[1].toLowerCase(); } return ''; }, getFileMime: function(fileName) { return this.mimes[this.getFileExtension(fileName)] || ''; } }; Mime.addMimeType(mimeData); return Mime; }); // Included from: src/javascript/core/utils/Dom.js /** * Dom.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/core/utils/Dom', ['moxie/core/utils/Env'], function(Env) { /** Get DOM Element by it's id. @method get @for Utils @param {String} id Identifier of the DOM Element @return {DOMElement} */ var get = function(id) { if (typeof id !== 'string') { return id; } return document.getElementById(id); }; /** Checks if specified DOM element has specified class. @method hasClass @static @param {Object} obj DOM element like object to add handler to. @param {String} name Class name */ var hasClass = function(obj, name) { if (!obj.className) { return false; } var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)"); return regExp.test(obj.className); }; /** Adds specified className to specified DOM element. @method addClass @static @param {Object} obj DOM element like object to add handler to. @param {String} name Class name */ var addClass = function(obj, name) { if (!hasClass(obj, name)) { obj.className = !obj.className ? name : obj.className.replace(/\s+$/, '') + ' ' + name; } }; /** Removes specified className from specified DOM element. @method removeClass @static @param {Object} obj DOM element like object to add handler to. @param {String} name Class name */ var removeClass = function(obj, name) { if (obj.className) { var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)"); obj.className = obj.className.replace(regExp, function($0, $1, $2) { return $1 === ' ' && $2 === ' ' ? ' ' : ''; }); } }; /** Returns a given computed style of a DOM element. @method getStyle @static @param {Object} obj DOM element like object. @param {String} name Style you want to get from the DOM element */ var getStyle = function(obj, name) { if (obj.currentStyle) { return obj.currentStyle[name]; } else if (window.getComputedStyle) { return window.getComputedStyle(obj, null)[name]; } }; /** Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields. @method getPos @static @param {Element} node HTML element or element id to get x, y position from. @param {Element} root Optional root element to stop calculations at. @return {object} Absolute position of the specified element object with x, y fields. */ var getPos = function(node, root) { var x = 0, y = 0, parent, doc = document, nodeRect, rootRect; node = node; root = root || doc.body; // Returns the x, y cordinate for an element on IE 6 and IE 7 function getIEPos(node) { var bodyElm, rect, x = 0, y = 0; if (node) { rect = node.getBoundingClientRect(); bodyElm = doc.compatMode === "CSS1Compat" ? doc.documentElement : doc.body; x = rect.left + bodyElm.scrollLeft; y = rect.top + bodyElm.scrollTop; } return { x : x, y : y }; } // Use getBoundingClientRect on IE 6 and IE 7 but not on IE 8 in standards mode if (node && node.getBoundingClientRect && Env.browser === 'IE' && (!doc.documentMode || doc.documentMode < 8)) { nodeRect = getIEPos(node); rootRect = getIEPos(root); return { x : nodeRect.x - rootRect.x, y : nodeRect.y - rootRect.y }; } parent = node; while (parent && parent != root && parent.nodeType) { x += parent.offsetLeft || 0; y += parent.offsetTop || 0; parent = parent.offsetParent; } parent = node.parentNode; while (parent && parent != root && parent.nodeType) { x -= parent.scrollLeft || 0; y -= parent.scrollTop || 0; parent = parent.parentNode; } return { x : x, y : y }; }; /** Returns the size of the specified node in pixels. @method getSize @static @param {Node} node Node to get the size of. @return {Object} Object with a w and h property. */ var getSize = function(node) { return { w : node.offsetWidth || node.clientWidth, h : node.offsetHeight || node.clientHeight }; }; return { get: get, hasClass: hasClass, addClass: addClass, removeClass: removeClass, getStyle: getStyle, getPos: getPos, getSize: getSize }; }); // Included from: src/javascript/core/Exceptions.js /** * Exceptions.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/core/Exceptions', [ 'moxie/core/utils/Basic' ], function(Basic) { function _findKey(obj, value) { var key; for (key in obj) { if (obj[key] === value) { return key; } } return null; } return { RuntimeError: (function() { var namecodes = { NOT_INIT_ERR: 1, NOT_SUPPORTED_ERR: 9, JS_ERR: 4 }; function RuntimeError(code) { this.code = code; this.name = _findKey(namecodes, code); this.message = this.name + ": RuntimeError " + this.code; } Basic.extend(RuntimeError, namecodes); RuntimeError.prototype = Error.prototype; return RuntimeError; }()), OperationNotAllowedException: (function() { function OperationNotAllowedException(code) { this.code = code; this.name = 'OperationNotAllowedException'; } Basic.extend(OperationNotAllowedException, { NOT_ALLOWED_ERR: 1 }); OperationNotAllowedException.prototype = Error.prototype; return OperationNotAllowedException; }()), ImageError: (function() { var namecodes = { WRONG_FORMAT: 1, MAX_RESOLUTION_ERR: 2, INVALID_META_ERR: 3 }; function ImageError(code) { this.code = code; this.name = _findKey(namecodes, code); this.message = this.name + ": ImageError " + this.code; } Basic.extend(ImageError, namecodes); ImageError.prototype = Error.prototype; return ImageError; }()), FileException: (function() { var namecodes = { NOT_FOUND_ERR: 1, SECURITY_ERR: 2, ABORT_ERR: 3, NOT_READABLE_ERR: 4, ENCODING_ERR: 5, NO_MODIFICATION_ALLOWED_ERR: 6, INVALID_STATE_ERR: 7, SYNTAX_ERR: 8 }; function FileException(code) { this.code = code; this.name = _findKey(namecodes, code); this.message = this.name + ": FileException " + this.code; } Basic.extend(FileException, namecodes); FileException.prototype = Error.prototype; return FileException; }()), DOMException: (function() { var namecodes = { INDEX_SIZE_ERR: 1, DOMSTRING_SIZE_ERR: 2, HIERARCHY_REQUEST_ERR: 3, WRONG_DOCUMENT_ERR: 4, INVALID_CHARACTER_ERR: 5, NO_DATA_ALLOWED_ERR: 6, NO_MODIFICATION_ALLOWED_ERR: 7, NOT_FOUND_ERR: 8, NOT_SUPPORTED_ERR: 9, INUSE_ATTRIBUTE_ERR: 10, INVALID_STATE_ERR: 11, SYNTAX_ERR: 12, INVALID_MODIFICATION_ERR: 13, NAMESPACE_ERR: 14, INVALID_ACCESS_ERR: 15, VALIDATION_ERR: 16, TYPE_MISMATCH_ERR: 17, SECURITY_ERR: 18, NETWORK_ERR: 19, ABORT_ERR: 20, URL_MISMATCH_ERR: 21, QUOTA_EXCEEDED_ERR: 22, TIMEOUT_ERR: 23, INVALID_NODE_TYPE_ERR: 24, DATA_CLONE_ERR: 25 }; function DOMException(code) { this.code = code; this.name = _findKey(namecodes, code); this.message = this.name + ": DOMException " + this.code; } Basic.extend(DOMException, namecodes); DOMException.prototype = Error.prototype; return DOMException; }()), EventException: (function() { function EventException(code) { this.code = code; this.name = 'EventException'; } Basic.extend(EventException, { UNSPECIFIED_EVENT_TYPE_ERR: 0 }); EventException.prototype = Error.prototype; return EventException; }()) }; }); // Included from: src/javascript/core/EventTarget.js /** * EventTarget.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/core/EventTarget', [ 'moxie/core/utils/Env', 'moxie/core/Exceptions', 'moxie/core/utils/Basic' ], function(Env, x, Basic) { /** Parent object for all event dispatching components and objects @class EventTarget @constructor EventTarget */ function EventTarget() { // hash of event listeners by object uid var eventpool = {}; Basic.extend(this, { /** Unique id of the event dispatcher, usually overriden by children @property uid @type String */ uid: null, /** Can be called from within a child in order to acquire uniqie id in automated manner @method init */ init: function() { if (!this.uid) { this.uid = Basic.guid('uid_'); } }, /** Register a handler to a specific event dispatched by the object @method addEventListener @param {String} type Type or basically a name of the event to subscribe to @param {Function} fn Callback function that will be called when event happens @param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first @param {Object} [scope=this] A scope to invoke event handler in */ addEventListener: function(type, fn, priority, scope) { var self = this, list; // without uid no event handlers can be added, so make sure we got one if (!this.hasOwnProperty('uid')) { this.uid = Basic.guid('uid_'); } type = Basic.trim(type); if (/\s/.test(type)) { // multiple event types were passed for one handler Basic.each(type.split(/\s+/), function(type) { self.addEventListener(type, fn, priority, scope); }); return; } type = type.toLowerCase(); priority = parseInt(priority, 10) || 0; list = eventpool[this.uid] && eventpool[this.uid][type] || []; list.push({fn : fn, priority : priority, scope : scope || this}); if (!eventpool[this.uid]) { eventpool[this.uid] = {}; } eventpool[this.uid][type] = list; }, /** Check if any handlers were registered to the specified event @method hasEventListener @param {String} type Type or basically a name of the event to check @return {Mixed} Returns a handler if it was found and false, if - not */ hasEventListener: function(type) { var list = type ? eventpool[this.uid] && eventpool[this.uid][type] : eventpool[this.uid]; return list ? list : false; }, /** Unregister the handler from the event, or if former was not specified - unregister all handlers @method removeEventListener @param {String} type Type or basically a name of the event @param {Function} [fn] Handler to unregister */ removeEventListener: function(type, fn) { type = type.toLowerCase(); var list = eventpool[this.uid] && eventpool[this.uid][type], i; if (list) { if (fn) { for (i = list.length - 1; i >= 0; i--) { if (list[i].fn === fn) { list.splice(i, 1); break; } } } else { list = []; } // delete event list if it has become empty if (!list.length) { delete eventpool[this.uid][type]; // and object specific entry in a hash if it has no more listeners attached if (Basic.isEmptyObj(eventpool[this.uid])) { delete eventpool[this.uid]; } } } }, /** Remove all event handlers from the object @method removeAllEventListeners */ removeAllEventListeners: function() { if (eventpool[this.uid]) { delete eventpool[this.uid]; } }, /** Dispatch the event @method dispatchEvent @param {String/Object} Type of event or event object to dispatch @param {Mixed} [...] Variable number of arguments to be passed to a handlers @return {Boolean} true by default and false if any handler returned false */ dispatchEvent: function(type) { var uid, list, args, tmpEvt, evt = {}, result = true, undef; if (Basic.typeOf(type) !== 'string') { // we can't use original object directly (because of Silverlight) tmpEvt = type; if (Basic.typeOf(tmpEvt.type) === 'string') { type = tmpEvt.type; if (tmpEvt.total !== undef && tmpEvt.loaded !== undef) { // progress event evt.total = tmpEvt.total; evt.loaded = tmpEvt.loaded; } evt.async = tmpEvt.async || false; } else { throw new x.EventException(x.EventException.UNSPECIFIED_EVENT_TYPE_ERR); } } // check if event is meant to be dispatched on an object having specific uid if (type.indexOf('::') !== -1) { (function(arr) { uid = arr[0]; type = arr[1]; }(type.split('::'))); } else { uid = this.uid; } type = type.toLowerCase(); list = eventpool[uid] && eventpool[uid][type]; if (list) { // sort event list by prority list.sort(function(a, b) { return b.priority - a.priority; }); args = [].slice.call(arguments); // first argument will be pseudo-event object args.shift(); evt.type = type; args.unshift(evt); if (MXI_DEBUG && Env.debug.events) { Env.log("Event '%s' fired on %u", evt.type, uid); } // Dispatch event to all listeners var queue = []; Basic.each(list, function(handler) { // explicitly set the target, otherwise events fired from shims do not get it args[0].target = handler.scope; // if event is marked as async, detach the handler if (evt.async) { queue.push(function(cb) { setTimeout(function() { cb(handler.fn.apply(handler.scope, args) === false); }, 1); }); } else { queue.push(function(cb) { cb(handler.fn.apply(handler.scope, args) === false); // if handler returns false stop propagation }); } }); if (queue.length) { Basic.inSeries(queue, function(err) { result = !err; }); } } return result; }, /** Alias for addEventListener @method bind @protected */ bind: function() { this.addEventListener.apply(this, arguments); }, /** Alias for removeEventListener @method unbind @protected */ unbind: function() { this.removeEventListener.apply(this, arguments); }, /** Alias for removeAllEventListeners @method unbindAll @protected */ unbindAll: function() { this.removeAllEventListeners.apply(this, arguments); }, /** Alias for dispatchEvent @method trigger @protected */ trigger: function() { return this.dispatchEvent.apply(this, arguments); }, /** Handle properties of on[event] type. @method handleEventProps @private */ handleEventProps: function(dispatches) { var self = this; this.bind(dispatches.join(' '), function(e) { var prop = 'on' + e.type.toLowerCase(); if (Basic.typeOf(this[prop]) === 'function') { this[prop].apply(this, arguments); } }); // object must have defined event properties, even if it doesn't make use of them Basic.each(dispatches, function(prop) { prop = 'on' + prop.toLowerCase(prop); if (Basic.typeOf(self[prop]) === 'undefined') { self[prop] = null; } }); } }); } EventTarget.instance = new EventTarget(); return EventTarget; }); // Included from: src/javascript/runtime/Runtime.js /** * Runtime.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/runtime/Runtime', [ "moxie/core/utils/Env", "moxie/core/utils/Basic", "moxie/core/utils/Dom", "moxie/core/EventTarget" ], function(Env, Basic, Dom, EventTarget) { var runtimeConstructors = {}, runtimes = {}; /** Common set of methods and properties for every runtime instance @class Runtime @param {Object} options @param {String} type Sanitized name of the runtime @param {Object} [caps] Set of capabilities that differentiate specified runtime @param {Object} [modeCaps] Set of capabilities that do require specific operational mode @param {String} [preferredMode='browser'] Preferred operational mode to choose if no required capabilities were requested */ function Runtime(options, type, caps, modeCaps, preferredMode) { /** Dispatched when runtime is initialized and ready. Results in RuntimeInit on a connected component. @event Init */ /** Dispatched when runtime fails to initialize. Results in RuntimeError on a connected component. @event Error */ var self = this , _shim , _uid = Basic.guid(type + '_') , defaultMode = preferredMode || 'browser' ; options = options || {}; // register runtime in private hash runtimes[_uid] = this; /** Default set of capabilities, which can be redifined later by specific runtime @private @property caps @type Object */ caps = Basic.extend({ // Runtime can: // provide access to raw binary data of the file access_binary: false, // provide access to raw binary data of the image (image extension is optional) access_image_binary: false, // display binary data as thumbs for example display_media: false, // make cross-domain requests do_cors: false, // accept files dragged and dropped from the desktop drag_and_drop: false, // filter files in selection dialog by their extensions filter_by_extension: true, // resize image (and manipulate it raw data of any file in general) resize_image: false, // periodically report how many bytes of total in the file were uploaded (loaded) report_upload_progress: false, // provide access to the headers of http response return_response_headers: false, // support response of specific type, which should be passed as an argument // e.g. runtime.can('return_response_type', 'blob') return_response_type: false, // return http status code of the response return_status_code: true, // send custom http header with the request send_custom_headers: false, // pick up the files from a dialog select_file: false, // select whole folder in file browse dialog select_folder: false, // select multiple files at once in file browse dialog select_multiple: true, // send raw binary data, that is generated after image resizing or manipulation of other kind send_binary_string: false, // send cookies with http request and therefore retain session send_browser_cookies: true, // send data formatted as multipart/form-data send_multipart: true, // slice the file or blob to smaller parts slice_blob: false, // upload file without preloading it to memory, stream it out directly from disk stream_upload: false, // programmatically trigger file browse dialog summon_file_dialog: false, // upload file of specific size, size should be passed as argument // e.g. runtime.can('upload_filesize', '500mb') upload_filesize: true, // initiate http request with specific http method, method should be passed as argument // e.g. runtime.can('use_http_method', 'put') use_http_method: true }, caps); // default to the mode that is compatible with preferred caps if (options.preferred_caps) { defaultMode = Runtime.getMode(modeCaps, options.preferred_caps, defaultMode); } if (MXI_DEBUG && Env.debug.runtime) { Env.log("\tdefault mode: %s", defaultMode); } // small extension factory here (is meant to be extended with actual extensions constructors) _shim = (function() { var objpool = {}; return { exec: function(uid, comp, fn, args) { if (_shim[comp]) { if (!objpool[uid]) { objpool[uid] = { context: this, instance: new _shim[comp]() }; } if (objpool[uid].instance[fn]) { return objpool[uid].instance[fn].apply(this, args); } } }, removeInstance: function(uid) { delete objpool[uid]; }, removeAllInstances: function() { var self = this; Basic.each(objpool, function(obj, uid) { if (Basic.typeOf(obj.instance.destroy) === 'function') { obj.instance.destroy.call(obj.context); } self.removeInstance(uid); }); } }; }()); // public methods Basic.extend(this, { /** Specifies whether runtime instance was initialized or not @property initialized @type {Boolean} @default false */ initialized: false, // shims require this flag to stop initialization retries /** Unique ID of the runtime @property uid @type {String} */ uid: _uid, /** Runtime type (e.g. flash, html5, etc) @property type @type {String} */ type: type, /** Runtime (not native one) may operate in browser or client mode. @property mode @private @type {String|Boolean} current mode or false, if none possible */ mode: Runtime.getMode(modeCaps, (options.required_caps), defaultMode), /** id of the DOM container for the runtime (if available) @property shimid @type {String} */ shimid: _uid + '_container', /** Number of connected clients. If equal to zero, runtime can be destroyed @property clients @type {Number} */ clients: 0, /** Runtime initialization options @property options @type {Object} */ options: options, /** Checks if the runtime has specific capability @method can @param {String} cap Name of capability to check @param {Mixed} [value] If passed, capability should somehow correlate to the value @param {Object} [refCaps] Set of capabilities to check the specified cap against (defaults to internal set) @return {Boolean} true if runtime has such capability and false, if - not */ can: function(cap, value) { var refCaps = arguments[2] || caps; // if cap var is a comma-separated list of caps, convert it to object (key/value) if (Basic.typeOf(cap) === 'string' && Basic.typeOf(value) === 'undefined') { cap = Runtime.parseCaps(cap); } if (Basic.typeOf(cap) === 'object') { for (var key in cap) { if (!this.can(key, cap[key], refCaps)) { return false; } } return true; } // check the individual cap if (Basic.typeOf(refCaps[cap]) === 'function') { return refCaps[cap].call(this, value); } else { return (value === refCaps[cap]); } }, /** Returns container for the runtime as DOM element @method getShimContainer @return {DOMElement} */ getShimContainer: function() { var container, shimContainer = Dom.get(this.shimid); // if no container for shim, create one if (!shimContainer) { container = this.options.container ? Dom.get(this.options.container) : document.body; // create shim container and insert it at an absolute position into the outer container shimContainer = document.createElement('div'); shimContainer.id = this.shimid; shimContainer.className = 'moxie-shim moxie-shim-' + this.type; Basic.extend(shimContainer.style, { position: 'absolute', top: '0px', left: '0px', width: '1px', height: '1px', overflow: 'hidden' }); container.appendChild(shimContainer); container = null; } return shimContainer; }, /** Returns runtime as DOM element (if appropriate) @method getShim @return {DOMElement} */ getShim: function() { return _shim; }, /** Invokes a method within the runtime itself (might differ across the runtimes) @method shimExec @param {Mixed} [] @protected @return {Mixed} Depends on the action and component */ shimExec: function(component, action) { var args = [].slice.call(arguments, 2); return self.getShim().exec.call(this, this.uid, component, action, args); }, /** Operaional interface that is used by components to invoke specific actions on the runtime (is invoked in the scope of component) @method exec @param {Mixed} []* @protected @return {Mixed} Depends on the action and component */ exec: function(component, action) { // this is called in the context of component, not runtime var args = [].slice.call(arguments, 2); if (self[component] && self[component][action]) { return self[component][action].apply(this, args); } return self.shimExec.apply(this, arguments); }, /** Destroys the runtime (removes all events and deletes DOM structures) @method destroy */ destroy: function() { if (!self) { return; // obviously already destroyed } var shimContainer = Dom.get(this.shimid); if (shimContainer) { shimContainer.parentNode.removeChild(shimContainer); } if (_shim) { _shim.removeAllInstances(); } this.unbindAll(); delete runtimes[this.uid]; this.uid = null; // mark this runtime as destroyed _uid = self = _shim = shimContainer = null; } }); // once we got the mode, test against all caps if (this.mode && options.required_caps && !this.can(options.required_caps)) { this.mode = false; } } /** Default order to try different runtime types @property order @type String @static */ Runtime.order = 'html5,html4'; /** Retrieves runtime from private hash by it's uid @method getRuntime @private @static @param {String} uid Unique identifier of the runtime @return {Runtime|Boolean} Returns runtime, if it exists and false, if - not */ Runtime.getRuntime = function(uid) { return runtimes[uid] ? runtimes[uid] : false; }; /** Register constructor for the Runtime of new (or perhaps modified) type @method addConstructor @static @param {String} type Runtime type (e.g. flash, html5, etc) @param {Function} construct Constructor for the Runtime type */ Runtime.addConstructor = function(type, constructor) { constructor.prototype = EventTarget.instance; runtimeConstructors[type] = constructor; }; /** Get the constructor for the specified type. method getConstructor @static @param {String} type Runtime type (e.g. flash, html5, etc) @return {Function} Constructor for the Runtime type */ Runtime.getConstructor = function(type) { return runtimeConstructors[type] || null; }; /** Get info about the runtime (uid, type, capabilities) @method getInfo @static @param {String} uid Unique identifier of the runtime @return {Mixed} Info object or null if runtime doesn't exist */ Runtime.getInfo = function(uid) { var runtime = Runtime.getRuntime(uid); if (runtime) { return { uid: runtime.uid, type: runtime.type, mode: runtime.mode, can: function() { return runtime.can.apply(runtime, arguments); } }; } return null; }; /** Convert caps represented by a comma-separated string to the object representation. @method parseCaps @static @param {String} capStr Comma-separated list of capabilities @return {Object} */ Runtime.parseCaps = function(capStr) { var capObj = {}; if (Basic.typeOf(capStr) !== 'string') { return capStr || {}; } Basic.each(capStr.split(','), function(key) { capObj[key] = true; // we assume it to be - true }); return capObj; }; /** Test the specified runtime for specific capabilities. @method can @static @param {String} type Runtime type (e.g. flash, html5, etc) @param {String|Object} caps Set of capabilities to check @return {Boolean} Result of the test */ Runtime.can = function(type, caps) { var runtime , constructor = Runtime.getConstructor(type) , mode ; if (constructor) { runtime = new constructor({ required_caps: caps }); mode = runtime.mode; runtime.destroy(); return !!mode; } return false; }; /** Figure out a runtime that supports specified capabilities. @method thatCan @static @param {String|Object} caps Set of capabilities to check @param {String} [runtimeOrder] Comma-separated list of runtimes to check against @return {String} Usable runtime identifier or null */ Runtime.thatCan = function(caps, runtimeOrder) { var types = (runtimeOrder || Runtime.order).split(/\s*,\s*/); for (var i in types) { if (Runtime.can(types[i], caps)) { return types[i]; } } return null; }; /** Figure out an operational mode for the specified set of capabilities. @method getMode @static @param {Object} modeCaps Set of capabilities that depend on particular runtime mode @param {Object} [requiredCaps] Supplied set of capabilities to find operational mode for @param {String|Boolean} [defaultMode='browser'] Default mode to use @return {String|Boolean} Compatible operational mode */ Runtime.getMode = function(modeCaps, requiredCaps, defaultMode) { var mode = null; if (Basic.typeOf(defaultMode) === 'undefined') { // only if not specified defaultMode = 'browser'; } if (requiredCaps && !Basic.isEmptyObj(modeCaps)) { // loop over required caps and check if they do require the same mode Basic.each(requiredCaps, function(value, cap) { if (modeCaps.hasOwnProperty(cap)) { var capMode = modeCaps[cap](value); // make sure we always have an array if (typeof(capMode) === 'string') { capMode = [capMode]; } if (!mode) { mode = capMode; } else if (!(mode = Basic.arrayIntersect(mode, capMode))) { // if cap requires conflicting mode - runtime cannot fulfill required caps if (MXI_DEBUG && Env.debug.runtime) { Env.log("\t\t%c: %v (conflicting mode requested: %s)", cap, value, capMode); } return (mode = false); } } if (MXI_DEBUG && Env.debug.runtime) { Env.log("\t\t%c: %v (compatible modes: %s)", cap, value, mode); } }); if (mode) { return Basic.inArray(defaultMode, mode) !== -1 ? defaultMode : mode[0]; } else if (mode === false) { return false; } } return defaultMode; }; /** Capability check that always returns true @private @static @return {True} */ Runtime.capTrue = function() { return true; }; /** Capability check that always returns false @private @static @return {False} */ Runtime.capFalse = function() { return false; }; /** Evaluate the expression to boolean value and create a function that always returns it. @private @static @param {Mixed} expr Expression to evaluate @return {Function} Function returning the result of evaluation */ Runtime.capTest = function(expr) { return function() { return !!expr; }; }; return Runtime; }); // Included from: src/javascript/runtime/RuntimeClient.js /** * RuntimeClient.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/runtime/RuntimeClient', [ 'moxie/core/utils/Env', 'moxie/core/Exceptions', 'moxie/core/utils/Basic', 'moxie/runtime/Runtime' ], function(Env, x, Basic, Runtime) { /** Set of methods and properties, required by a component to acquire ability to connect to a runtime @class RuntimeClient */ return function RuntimeClient() { var runtime; Basic.extend(this, { /** Connects to the runtime specified by the options. Will either connect to existing runtime or create a new one. Increments number of clients connected to the specified runtime. @private @method connectRuntime @param {Mixed} options Can be a runtme uid or a set of key-value pairs defining requirements and pre-requisites */ connectRuntime: function(options) { var comp = this, ruid; function initialize(items) { var type, constructor; // if we ran out of runtimes if (!items.length) { comp.trigger('RuntimeError', new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR)); runtime = null; return; } type = items.shift().toLowerCase(); constructor = Runtime.getConstructor(type); if (!constructor) { initialize(items); return; } if (MXI_DEBUG && Env.debug.runtime) { Env.log("Trying runtime: %s", type); Env.log(options); } // try initializing the runtime runtime = new constructor(options); runtime.bind('Init', function() { // mark runtime as initialized runtime.initialized = true; if (MXI_DEBUG && Env.debug.runtime) { Env.log("Runtime '%s' initialized", runtime.type); } // jailbreak ... setTimeout(function() { runtime.clients++; // this will be triggered on component comp.trigger('RuntimeInit', runtime); }, 1); }); runtime.bind('Error', function() { if (MXI_DEBUG && Env.debug.runtime) { Env.log("Runtime '%s' failed to initialize", runtime.type); } runtime.destroy(); // runtime cannot destroy itself from inside at a right moment, thus we do it here initialize(items); }); /*runtime.bind('Exception', function() { });*/ if (MXI_DEBUG && Env.debug.runtime) { Env.log("\tselected mode: %s", runtime.mode); } // check if runtime managed to pick-up operational mode if (!runtime.mode) { runtime.trigger('Error'); return; } runtime.init(); } // check if a particular runtime was requested if (Basic.typeOf(options) === 'string') { ruid = options; } else if (Basic.typeOf(options.ruid) === 'string') { ruid = options.ruid; } if (ruid) { runtime = Runtime.getRuntime(ruid); if (runtime) { runtime.clients++; return runtime; } else { // there should be a runtime and there's none - weird case throw new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR); } } // initialize a fresh one, that fits runtime list and required features best initialize((options.runtime_order || Runtime.order).split(/\s*,\s*/)); }, /** Disconnects from the runtime. Decrements number of clients connected to the specified runtime. @private @method disconnectRuntime */ disconnectRuntime: function() { if (runtime && --runtime.clients <= 0) { runtime.destroy(); } // once the component is disconnected, it shouldn't have access to the runtime runtime = null; }, /** Returns the runtime to which the client is currently connected. @method getRuntime @return {Runtime} Runtime or null if client is not connected */ getRuntime: function() { if (runtime && runtime.uid) { return runtime; } return runtime = null; // make sure we do not leave zombies rambling around }, /** Handy shortcut to safely invoke runtime extension methods. @private @method exec @return {Mixed} Whatever runtime extension method returns */ exec: function() { if (runtime) { return runtime.exec.apply(this, arguments); } return null; } }); }; }); // Included from: src/javascript/file/FileInput.js /** * FileInput.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/file/FileInput', [ 'moxie/core/utils/Basic', 'moxie/core/utils/Env', 'moxie/core/utils/Mime', 'moxie/core/utils/Dom', 'moxie/core/Exceptions', 'moxie/core/EventTarget', 'moxie/core/I18n', 'moxie/runtime/Runtime', 'moxie/runtime/RuntimeClient' ], function(Basic, Env, Mime, Dom, x, EventTarget, I18n, Runtime, RuntimeClient) { /** Provides a convenient way to create cross-browser file-picker. Generates file selection dialog on click, converts selected files to _File_ objects, to be used in conjunction with _Image_, preloaded in memory with _FileReader_ or uploaded to a server through _XMLHttpRequest_. @class FileInput @constructor @extends EventTarget @uses RuntimeClient @param {Object|String|DOMElement} options If options is string or node, argument is considered as _browse\_button_. @param {String|DOMElement} options.browse_button DOM Element to turn into file picker. @param {Array} [options.accept] Array of mime types to accept. By default accepts all. @param {String} [options.file='file'] Name of the file field (not the filename). @param {Boolean} [options.multiple=false] Enable selection of multiple files. @param {Boolean} [options.directory=false] Turn file input into the folder input (cannot be both at the same time). @param {String|DOMElement} [options.container] DOM Element to use as a container for file-picker. Defaults to parentNode for _browse\_button_. @param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support. @example
*/ var dispatches = [ /** Dispatched when runtime is connected and file-picker is ready to be used. @event ready @param {Object} event */ 'ready', /** Dispatched right after [ready](#event_ready) event, and whenever [refresh()](#method_refresh) is invoked. Check [corresponding documentation entry](#method_refresh) for more info. @event refresh @param {Object} event */ /** Dispatched when selection of files in the dialog is complete. @event change @param {Object} event */ 'change', 'cancel', // TODO: might be useful /** Dispatched when mouse cursor enters file-picker area. Can be used to style element accordingly. @event mouseenter @param {Object} event */ 'mouseenter', /** Dispatched when mouse cursor leaves file-picker area. Can be used to style element accordingly. @event mouseleave @param {Object} event */ 'mouseleave', /** Dispatched when functional mouse button is pressed on top of file-picker area. @event mousedown @param {Object} event */ 'mousedown', /** Dispatched when functional mouse button is released on top of file-picker area. @event mouseup @param {Object} event */ 'mouseup' ]; function FileInput(options) { if (MXI_DEBUG) { Env.log("Instantiating FileInput..."); } var self = this, container, browseButton, defaults; // if flat argument passed it should be browse_button id if (Basic.inArray(Basic.typeOf(options), ['string', 'node']) !== -1) { options = { browse_button : options }; } // this will help us to find proper default container browseButton = Dom.get(options.browse_button); if (!browseButton) { // browse button is required throw new x.DOMException(x.DOMException.NOT_FOUND_ERR); } // figure out the options defaults = { accept: [{ title: I18n.translate('All Files'), extensions: '*' }], name: 'file', multiple: false, required_caps: false, container: browseButton.parentNode || document.body }; options = Basic.extend({}, defaults, options); // convert to object representation if (typeof(options.required_caps) === 'string') { options.required_caps = Runtime.parseCaps(options.required_caps); } // normalize accept option (could be list of mime types or array of title/extensions pairs) if (typeof(options.accept) === 'string') { options.accept = Mime.mimes2extList(options.accept); } container = Dom.get(options.container); // make sure we have container if (!container) { container = document.body; } // make container relative, if it's not if (Dom.getStyle(container, 'position') === 'static') { container.style.position = 'relative'; } container = browseButton = null; // IE RuntimeClient.call(self); Basic.extend(self, { /** Unique id of the component @property uid @protected @readOnly @type {String} @default UID */ uid: Basic.guid('uid_'), /** Unique id of the connected runtime, if any. @property ruid @protected @type {String} */ ruid: null, /** Unique id of the runtime container. Useful to get hold of it for various manipulations. @property shimid @protected @type {String} */ shimid: null, /** Array of selected mOxie.File objects @property files @type {Array} @default null */ files: null, /** Initializes the file-picker, connects it to runtime and dispatches event ready when done. @method init */ init: function() { self.bind('RuntimeInit', function(e, runtime) { self.ruid = runtime.uid; self.shimid = runtime.shimid; self.bind("Ready", function() { self.trigger("Refresh"); }, 999); // re-position and resize shim container self.bind('Refresh', function() { var pos, size, browseButton, shimContainer; browseButton = Dom.get(options.browse_button); shimContainer = Dom.get(runtime.shimid); // do not use runtime.getShimContainer(), since it will create container if it doesn't exist if (browseButton) { pos = Dom.getPos(browseButton, Dom.get(options.container)); size = Dom.getSize(browseButton); if (shimContainer) { Basic.extend(shimContainer.style, { top : pos.y + 'px', left : pos.x + 'px', width : size.w + 'px', height : size.h + 'px' }); } } shimContainer = browseButton = null; }); runtime.exec.call(self, 'FileInput', 'init', options); }); // runtime needs: options.required_features, options.runtime_order and options.container self.connectRuntime(Basic.extend({}, options, { required_caps: { select_file: true } })); }, /** Disables file-picker element, so that it doesn't react to mouse clicks. @method disable @param {Boolean} [state=true] Disable component if - true, enable if - false */ disable: function(state) { var runtime = this.getRuntime(); if (runtime) { runtime.exec.call(this, 'FileInput', 'disable', Basic.typeOf(state) === 'undefined' ? true : state); } }, /** Reposition and resize dialog trigger to match the position and size of browse_button element. @method refresh */ refresh: function() { self.trigger("Refresh"); }, /** Destroy component. @method destroy */ destroy: function() { var runtime = this.getRuntime(); if (runtime) { runtime.exec.call(this, 'FileInput', 'destroy'); this.disconnectRuntime(); } if (Basic.typeOf(this.files) === 'array') { // no sense in leaving associated files behind Basic.each(this.files, function(file) { file.destroy(); }); } this.files = null; this.unbindAll(); } }); this.handleEventProps(dispatches); } FileInput.prototype = EventTarget.instance; return FileInput; }); // Included from: src/javascript/core/utils/Encode.js /** * Encode.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/core/utils/Encode', [], function() { /** Encode string with UTF-8 @method utf8_encode @for Utils @static @param {String} str String to encode @return {String} UTF-8 encoded string */ var utf8_encode = function(str) { return unescape(encodeURIComponent(str)); }; /** Decode UTF-8 encoded string @method utf8_decode @static @param {String} str String to decode @return {String} Decoded string */ var utf8_decode = function(str_data) { return decodeURIComponent(escape(str_data)); }; /** Decode Base64 encoded string (uses browser's default method if available), from: https://raw.github.com/kvz/phpjs/master/functions/url/base64_decode.js @method atob @static @param {String} data String to decode @return {String} Decoded string */ var atob = function(data, utf8) { if (typeof(window.atob) === 'function') { return utf8 ? utf8_decode(window.atob(data)) : window.atob(data); } // http://kevin.vanzonneveld.net // + original by: Tyler Akins (http://rumkin.com) // + improved by: Thunder.m // + input by: Aman Gupta // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + bugfixed by: Onno Marsman // + bugfixed by: Pellentesque Malesuada // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + input by: Brett Zamir (http://brett-zamir.me) // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // * example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA=='); // * returns 1: 'Kevin van Zonneveld' // mozilla has this native // - but breaks in 2.0.0.12! //if (typeof this.window.atob == 'function') { // return atob(data); //} var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, dec = "", tmp_arr = []; if (!data) { return data; } data += ''; do { // unpack four hexets into three octets using index points in b64 h1 = b64.indexOf(data.charAt(i++)); h2 = b64.indexOf(data.charAt(i++)); h3 = b64.indexOf(data.charAt(i++)); h4 = b64.indexOf(data.charAt(i++)); bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; o1 = bits >> 16 & 0xff; o2 = bits >> 8 & 0xff; o3 = bits & 0xff; if (h3 == 64) { tmp_arr[ac++] = String.fromCharCode(o1); } else if (h4 == 64) { tmp_arr[ac++] = String.fromCharCode(o1, o2); } else { tmp_arr[ac++] = String.fromCharCode(o1, o2, o3); } } while (i < data.length); dec = tmp_arr.join(''); return utf8 ? utf8_decode(dec) : dec; }; /** Base64 encode string (uses browser's default method if available), from: https://raw.github.com/kvz/phpjs/master/functions/url/base64_encode.js @method btoa @static @param {String} data String to encode @return {String} Base64 encoded string */ var btoa = function(data, utf8) { if (utf8) { data = utf8_encode(data); } if (typeof(window.btoa) === 'function') { return window.btoa(data); } // http://kevin.vanzonneveld.net // + original by: Tyler Akins (http://rumkin.com) // + improved by: Bayron Guevara // + improved by: Thunder.m // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + bugfixed by: Pellentesque Malesuada // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + improved by: Rafał Kukawski (http://kukawski.pl) // * example 1: base64_encode('Kevin van Zonneveld'); // * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA==' // mozilla has this native // - but breaks in 2.0.0.12! var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc = "", tmp_arr = []; if (!data) { return data; } do { // pack three octets into four hexets o1 = data.charCodeAt(i++); o2 = data.charCodeAt(i++); o3 = data.charCodeAt(i++); bits = o1 << 16 | o2 << 8 | o3; h1 = bits >> 18 & 0x3f; h2 = bits >> 12 & 0x3f; h3 = bits >> 6 & 0x3f; h4 = bits & 0x3f; // use hexets to index into b64, and append result to encoded string tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); } while (i < data.length); enc = tmp_arr.join(''); var r = data.length % 3; return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3); }; return { utf8_encode: utf8_encode, utf8_decode: utf8_decode, atob: atob, btoa: btoa }; }); // Included from: src/javascript/file/Blob.js /** * Blob.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/file/Blob', [ 'moxie/core/utils/Basic', 'moxie/core/utils/Encode', 'moxie/runtime/RuntimeClient' ], function(Basic, Encode, RuntimeClient) { var blobpool = {}; /** @class Blob @constructor @param {String} ruid Unique id of the runtime, to which this blob belongs to @param {Object} blob Object "Native" blob object, as it is represented in the runtime */ function Blob(ruid, blob) { function _sliceDetached(start, end, type) { var blob, data = blobpool[this.uid]; if (Basic.typeOf(data) !== 'string' || !data.length) { return null; // or throw exception } blob = new Blob(null, { type: type, size: end - start }); blob.detach(data.substr(start, blob.size)); return blob; } RuntimeClient.call(this); if (ruid) { this.connectRuntime(ruid); } if (!blob) { blob = {}; } else if (Basic.typeOf(blob) === 'string') { // dataUrl or binary string blob = { data: blob }; } Basic.extend(this, { /** Unique id of the component @property uid @type {String} */ uid: blob.uid || Basic.guid('uid_'), /** Unique id of the connected runtime, if falsy, then runtime will have to be initialized before this Blob can be used, modified or sent @property ruid @type {String} */ ruid: ruid, /** Size of blob @property size @type {Number} @default 0 */ size: blob.size || 0, /** Mime type of blob @property type @type {String} @default '' */ type: blob.type || '', /** @method slice @param {Number} [start=0] */ slice: function(start, end, type) { if (this.isDetached()) { return _sliceDetached.apply(this, arguments); } return this.getRuntime().exec.call(this, 'Blob', 'slice', this.getSource(), start, end, type); }, /** Returns "native" blob object (as it is represented in connected runtime) or null if not found @method getSource @return {Blob} Returns "native" blob object or null if not found */ getSource: function() { if (!blobpool[this.uid]) { return null; } return blobpool[this.uid]; }, /** Detaches blob from any runtime that it depends on and initialize with standalone value @method detach @protected @param {DOMString} [data=''] Standalone value */ detach: function(data) { if (this.ruid) { this.getRuntime().exec.call(this, 'Blob', 'destroy'); this.disconnectRuntime(); this.ruid = null; } data = data || ''; // if dataUrl, convert to binary string if (data.substr(0, 5) == 'data:') { var base64Offset = data.indexOf(';base64,'); this.type = data.substring(5, base64Offset); data = Encode.atob(data.substring(base64Offset + 8)); } this.size = data.length; blobpool[this.uid] = data; }, /** Checks if blob is standalone (detached of any runtime) @method isDetached @protected @return {Boolean} */ isDetached: function() { return !this.ruid && Basic.typeOf(blobpool[this.uid]) === 'string'; }, /** Destroy Blob and free any resources it was using @method destroy */ destroy: function() { this.detach(); delete blobpool[this.uid]; } }); if (blob.data) { this.detach(blob.data); // auto-detach if payload has been passed } else { blobpool[this.uid] = blob; } } return Blob; }); // Included from: src/javascript/file/File.js /** * File.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/file/File', [ 'moxie/core/utils/Basic', 'moxie/core/utils/Mime', 'moxie/file/Blob' ], function(Basic, Mime, Blob) { /** @class File @extends Blob @constructor @param {String} ruid Unique id of the runtime, to which this blob belongs to @param {Object} file Object "Native" file object, as it is represented in the runtime */ function File(ruid, file) { if (!file) { // avoid extra errors in case we overlooked something file = {}; } Blob.apply(this, arguments); if (!this.type) { this.type = Mime.getFileMime(file.name); } // sanitize file name or generate new one var name; if (file.name) { name = file.name.replace(/\\/g, '/'); name = name.substr(name.lastIndexOf('/') + 1); } else if (this.type) { var prefix = this.type.split('/')[0]; name = Basic.guid((prefix !== '' ? prefix : 'file') + '_'); if (Mime.extensions[this.type]) { name += '.' + Mime.extensions[this.type][0]; // append proper extension if possible } } Basic.extend(this, { /** File name @property name @type {String} @default UID */ name: name || Basic.guid('file_'), /** Relative path to the file inside a directory @property relativePath @type {String} @default '' */ relativePath: '', /** Date of last modification @property lastModifiedDate @type {String} @default now */ lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString() // Thu Aug 23 2012 19:40:00 GMT+0400 (GET) }); } File.prototype = Blob.prototype; return File; }); // Included from: src/javascript/file/FileDrop.js /** * FileDrop.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/file/FileDrop', [ 'moxie/core/I18n', 'moxie/core/utils/Dom', 'moxie/core/Exceptions', 'moxie/core/utils/Basic', 'moxie/core/utils/Env', 'moxie/file/File', 'moxie/runtime/RuntimeClient', 'moxie/core/EventTarget', 'moxie/core/utils/Mime' ], function(I18n, Dom, x, Basic, Env, File, RuntimeClient, EventTarget, Mime) { /** Turn arbitrary DOM element to a drop zone accepting files. Converts selected files to _File_ objects, to be used in conjunction with _Image_, preloaded in memory with _FileReader_ or uploaded to a server through _XMLHttpRequest_. @example
Drop files here

@class FileDrop @constructor @extends EventTarget @uses RuntimeClient @param {Object|String} options If options has typeof string, argument is considered as options.drop_zone @param {String|DOMElement} options.drop_zone DOM Element to turn into a drop zone @param {Array} [options.accept] Array of mime types to accept. By default accepts all @param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support */ var dispatches = [ /** Dispatched when runtime is connected and drop zone is ready to accept files. @event ready @param {Object} event */ 'ready', /** Dispatched when dragging cursor enters the drop zone. @event dragenter @param {Object} event */ 'dragenter', /** Dispatched when dragging cursor leaves the drop zone. @event dragleave @param {Object} event */ 'dragleave', /** Dispatched when file is dropped onto the drop zone. @event drop @param {Object} event */ 'drop', /** Dispatched if error occurs. @event error @param {Object} event */ 'error' ]; function FileDrop(options) { if (MXI_DEBUG) { Env.log("Instantiating FileDrop..."); } var self = this, defaults; // if flat argument passed it should be drop_zone id if (typeof(options) === 'string') { options = { drop_zone : options }; } // figure out the options defaults = { accept: [{ title: I18n.translate('All Files'), extensions: '*' }], required_caps: { drag_and_drop: true } }; options = typeof(options) === 'object' ? Basic.extend({}, defaults, options) : defaults; // this will help us to find proper default container options.container = Dom.get(options.drop_zone) || document.body; // make container relative, if it is not if (Dom.getStyle(options.container, 'position') === 'static') { options.container.style.position = 'relative'; } // normalize accept option (could be list of mime types or array of title/extensions pairs) if (typeof(options.accept) === 'string') { options.accept = Mime.mimes2extList(options.accept); } RuntimeClient.call(self); Basic.extend(self, { uid: Basic.guid('uid_'), ruid: null, files: null, init: function() { self.bind('RuntimeInit', function(e, runtime) { self.ruid = runtime.uid; runtime.exec.call(self, 'FileDrop', 'init', options); self.dispatchEvent('ready'); }); // runtime needs: options.required_features, options.runtime_order and options.container self.connectRuntime(options); // throws RuntimeError }, destroy: function() { var runtime = this.getRuntime(); if (runtime) { runtime.exec.call(this, 'FileDrop', 'destroy'); this.disconnectRuntime(); } this.files = null; this.unbindAll(); } }); this.handleEventProps(dispatches); } FileDrop.prototype = EventTarget.instance; return FileDrop; }); // Included from: src/javascript/file/FileReader.js /** * FileReader.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/file/FileReader', [ 'moxie/core/utils/Basic', 'moxie/core/utils/Encode', 'moxie/core/Exceptions', 'moxie/core/EventTarget', 'moxie/file/Blob', 'moxie/runtime/RuntimeClient' ], function(Basic, Encode, x, EventTarget, Blob, RuntimeClient) { /** Utility for preloading o.Blob/o.File objects in memory. By design closely follows [W3C FileReader](http://www.w3.org/TR/FileAPI/#dfn-filereader) interface. Where possible uses native FileReader, where - not falls back to shims. @class FileReader @constructor FileReader @extends EventTarget @uses RuntimeClient */ var dispatches = [ /** Dispatched when the read starts. @event loadstart @param {Object} event */ 'loadstart', /** Dispatched while reading (and decoding) blob, and reporting partial Blob data (progess.loaded/progress.total). @event progress @param {Object} event */ 'progress', /** Dispatched when the read has successfully completed. @event load @param {Object} event */ 'load', /** Dispatched when the read has been aborted. For instance, by invoking the abort() method. @event abort @param {Object} event */ 'abort', /** Dispatched when the read has failed. @event error @param {Object} event */ 'error', /** Dispatched when the request has completed (either in success or failure). @event loadend @param {Object} event */ 'loadend' ]; function FileReader() { RuntimeClient.call(this); Basic.extend(this, { /** UID of the component instance. @property uid @type {String} */ uid: Basic.guid('uid_'), /** Contains current state of FileReader object. Can take values of FileReader.EMPTY, FileReader.LOADING and FileReader.DONE. @property readyState @type {Number} @default FileReader.EMPTY */ readyState: FileReader.EMPTY, /** Result of the successful read operation. @property result @type {String} */ result: null, /** Stores the error of failed asynchronous read operation. @property error @type {DOMError} */ error: null, /** Initiates reading of File/Blob object contents to binary string. @method readAsBinaryString @param {Blob|File} blob Object to preload */ readAsBinaryString: function(blob) { _read.call(this, 'readAsBinaryString', blob); }, /** Initiates reading of File/Blob object contents to dataURL string. @method readAsDataURL @param {Blob|File} blob Object to preload */ readAsDataURL: function(blob) { _read.call(this, 'readAsDataURL', blob); }, /** Initiates reading of File/Blob object contents to string. @method readAsText @param {Blob|File} blob Object to preload */ readAsText: function(blob) { _read.call(this, 'readAsText', blob); }, /** Aborts preloading process. @method abort */ abort: function() { this.result = null; if (Basic.inArray(this.readyState, [FileReader.EMPTY, FileReader.DONE]) !== -1) { return; } else if (this.readyState === FileReader.LOADING) { this.readyState = FileReader.DONE; } this.exec('FileReader', 'abort'); this.trigger('abort'); this.trigger('loadend'); }, /** Destroy component and release resources. @method destroy */ destroy: function() { this.abort(); this.exec('FileReader', 'destroy'); this.disconnectRuntime(); this.unbindAll(); } }); // uid must already be assigned this.handleEventProps(dispatches); this.bind('Error', function(e, err) { this.readyState = FileReader.DONE; this.error = err; }, 999); this.bind('Load', function(e) { this.readyState = FileReader.DONE; }, 999); function _read(op, blob) { var self = this; this.trigger('loadstart'); if (this.readyState === FileReader.LOADING) { this.trigger('error', new x.DOMException(x.DOMException.INVALID_STATE_ERR)); this.trigger('loadend'); return; } // if source is not o.Blob/o.File if (!(blob instanceof Blob)) { this.trigger('error', new x.DOMException(x.DOMException.NOT_FOUND_ERR)); this.trigger('loadend'); return; } this.result = null; this.readyState = FileReader.LOADING; if (blob.isDetached()) { var src = blob.getSource(); switch (op) { case 'readAsText': case 'readAsBinaryString': this.result = src; break; case 'readAsDataURL': this.result = 'data:' + blob.type + ';base64,' + Encode.btoa(src); break; } this.readyState = FileReader.DONE; this.trigger('load'); this.trigger('loadend'); } else { this.connectRuntime(blob.ruid); this.exec('FileReader', 'read', op, blob); } } } /** Initial FileReader state @property EMPTY @type {Number} @final @static @default 0 */ FileReader.EMPTY = 0; /** FileReader switches to this state when it is preloading the source @property LOADING @type {Number} @final @static @default 1 */ FileReader.LOADING = 1; /** Preloading is complete, this is a final state @property DONE @type {Number} @final @static @default 2 */ FileReader.DONE = 2; FileReader.prototype = EventTarget.instance; return FileReader; }); // Included from: src/javascript/core/utils/Url.js /** * Url.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/core/utils/Url', [], function() { /** Parse url into separate components and fill in absent parts with parts from current url, based on https://raw.github.com/kvz/phpjs/master/functions/url/parse_url.js @method parseUrl @for Utils @static @param {String} url Url to parse (defaults to empty string if undefined) @return {Object} Hash containing extracted uri components */ var parseUrl = function(url, currentUrl) { var key = ['source', 'scheme', 'authority', 'userInfo', 'user', 'pass', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment'] , i = key.length , ports = { http: 80, https: 443 } , uri = {} , regex = /^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/ , m = regex.exec(url || '') ; while (i--) { if (m[i]) { uri[key[i]] = m[i]; } } // when url is relative, we set the origin and the path ourselves if (!uri.scheme) { // come up with defaults if (!currentUrl || typeof(currentUrl) === 'string') { currentUrl = parseUrl(currentUrl || document.location.href); } uri.scheme = currentUrl.scheme; uri.host = currentUrl.host; uri.port = currentUrl.port; var path = ''; // for urls without trailing slash we need to figure out the path if (/^[^\/]/.test(uri.path)) { path = currentUrl.path; // if path ends with a filename, strip it if (/\/[^\/]*\.[^\/]*$/.test(path)) { path = path.replace(/\/[^\/]+$/, '/'); } else { // avoid double slash at the end (see #127) path = path.replace(/\/?$/, '/'); } } uri.path = path + (uri.path || ''); // site may reside at domain.com or domain.com/subdir } if (!uri.port) { uri.port = ports[uri.scheme] || 80; } uri.port = parseInt(uri.port, 10); if (!uri.path) { uri.path = "/"; } delete uri.source; return uri; }; /** Resolve url - among other things will turn relative url to absolute @method resolveUrl @static @param {String|Object} url Either absolute or relative, or a result of parseUrl call @return {String} Resolved, absolute url */ var resolveUrl = function(url) { var ports = { // we ignore default ports http: 80, https: 443 } , urlp = typeof(url) === 'object' ? url : parseUrl(url); ; return urlp.scheme + '://' + urlp.host + (urlp.port !== ports[urlp.scheme] ? ':' + urlp.port : '') + urlp.path + (urlp.query ? urlp.query : ''); }; /** Check if specified url has the same origin as the current document @method hasSameOrigin @param {String|Object} url @return {Boolean} */ var hasSameOrigin = function(url) { function origin(url) { return [url.scheme, url.host, url.port].join('/'); } if (typeof url === 'string') { url = parseUrl(url); } return origin(parseUrl()) === origin(url); }; return { parseUrl: parseUrl, resolveUrl: resolveUrl, hasSameOrigin: hasSameOrigin }; }); // Included from: src/javascript/runtime/RuntimeTarget.js /** * RuntimeTarget.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/runtime/RuntimeTarget', [ 'moxie/core/utils/Basic', 'moxie/runtime/RuntimeClient', "moxie/core/EventTarget" ], function(Basic, RuntimeClient, EventTarget) { /** Instance of this class can be used as a target for the events dispatched by shims, when allowing them onto components is for either reason inappropriate @class RuntimeTarget @constructor @protected @extends EventTarget */ function RuntimeTarget() { this.uid = Basic.guid('uid_'); RuntimeClient.call(this); this.destroy = function() { this.disconnectRuntime(); this.unbindAll(); }; } RuntimeTarget.prototype = EventTarget.instance; return RuntimeTarget; }); // Included from: src/javascript/file/FileReaderSync.js /** * FileReaderSync.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/file/FileReaderSync', [ 'moxie/core/utils/Basic', 'moxie/runtime/RuntimeClient', 'moxie/core/utils/Encode' ], function(Basic, RuntimeClient, Encode) { /** Synchronous FileReader implementation. Something like this is available in WebWorkers environment, here it can be used to read only preloaded blobs/files and only below certain size (not yet sure what that'd be, but probably < 1mb). Not meant to be used directly by user. @class FileReaderSync @private @constructor */ return function() { RuntimeClient.call(this); Basic.extend(this, { uid: Basic.guid('uid_'), readAsBinaryString: function(blob) { return _read.call(this, 'readAsBinaryString', blob); }, readAsDataURL: function(blob) { return _read.call(this, 'readAsDataURL', blob); }, /*readAsArrayBuffer: function(blob) { return _read.call(this, 'readAsArrayBuffer', blob); },*/ readAsText: function(blob) { return _read.call(this, 'readAsText', blob); } }); function _read(op, blob) { if (blob.isDetached()) { var src = blob.getSource(); switch (op) { case 'readAsBinaryString': return src; case 'readAsDataURL': return 'data:' + blob.type + ';base64,' + Encode.btoa(src); case 'readAsText': var txt = ''; for (var i = 0, length = src.length; i < length; i++) { txt += String.fromCharCode(src[i]); } return txt; } } else { var result = this.connectRuntime(blob.ruid).exec.call(this, 'FileReaderSync', 'read', op, blob); this.disconnectRuntime(); return result; } } }; }); // Included from: src/javascript/xhr/FormData.js /** * FormData.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define("moxie/xhr/FormData", [ "moxie/core/Exceptions", "moxie/core/utils/Basic", "moxie/file/Blob" ], function(x, Basic, Blob) { /** FormData @class FormData @constructor */ function FormData() { var _blob, _fields = []; Basic.extend(this, { /** Append another key-value pair to the FormData object @method append @param {String} name Name for the new field @param {String|Blob|Array|Object} value Value for the field */ append: function(name, value) { var self = this, valueType = Basic.typeOf(value); // according to specs value might be either Blob or String if (value instanceof Blob) { _blob = { name: name, value: value // unfortunately we can only send single Blob in one FormData }; } else if ('array' === valueType) { name += '[]'; Basic.each(value, function(value) { self.append(name, value); }); } else if ('object' === valueType) { Basic.each(value, function(value, key) { self.append(name + '[' + key + ']', value); }); } else if ('null' === valueType || 'undefined' === valueType || 'number' === valueType && isNaN(value)) { self.append(name, "false"); } else { _fields.push({ name: name, value: value.toString() }); } }, /** Checks if FormData contains Blob. @method hasBlob @return {Boolean} */ hasBlob: function() { return !!this.getBlob(); }, /** Retrieves blob. @method getBlob @return {Object} Either Blob if found or null */ getBlob: function() { return _blob && _blob.value || null; }, /** Retrieves blob field name. @method getBlobName @return {String} Either Blob field name or null */ getBlobName: function() { return _blob && _blob.name || null; }, /** Loop over the fields in FormData and invoke the callback for each of them. @method each @param {Function} cb Callback to call for each field */ each: function(cb) { Basic.each(_fields, function(field) { cb(field.value, field.name); }); if (_blob) { cb(_blob.value, _blob.name); } }, destroy: function() { _blob = null; _fields = []; } }); } return FormData; }); // Included from: src/javascript/xhr/XMLHttpRequest.js /** * XMLHttpRequest.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define("moxie/xhr/XMLHttpRequest", [ "moxie/core/utils/Basic", "moxie/core/Exceptions", "moxie/core/EventTarget", "moxie/core/utils/Encode", "moxie/core/utils/Url", "moxie/runtime/Runtime", "moxie/runtime/RuntimeTarget", "moxie/file/Blob", "moxie/file/FileReaderSync", "moxie/xhr/FormData", "moxie/core/utils/Env", "moxie/core/utils/Mime" ], function(Basic, x, EventTarget, Encode, Url, Runtime, RuntimeTarget, Blob, FileReaderSync, FormData, Env, Mime) { var httpCode = { 100: 'Continue', 101: 'Switching Protocols', 102: 'Processing', 200: 'OK', 201: 'Created', 202: 'Accepted', 203: 'Non-Authoritative Information', 204: 'No Content', 205: 'Reset Content', 206: 'Partial Content', 207: 'Multi-Status', 226: 'IM Used', 300: 'Multiple Choices', 301: 'Moved Permanently', 302: 'Found', 303: 'See Other', 304: 'Not Modified', 305: 'Use Proxy', 306: 'Reserved', 307: 'Temporary Redirect', 400: 'Bad Request', 401: 'Unauthorized', 402: 'Payment Required', 403: 'Forbidden', 404: 'Not Found', 405: 'Method Not Allowed', 406: 'Not Acceptable', 407: 'Proxy Authentication Required', 408: 'Request Timeout', 409: 'Conflict', 410: 'Gone', 411: 'Length Required', 412: 'Precondition Failed', 413: 'Request Entity Too Large', 414: 'Request-URI Too Long', 415: 'Unsupported Media Type', 416: 'Requested Range Not Satisfiable', 417: 'Expectation Failed', 422: 'Unprocessable Entity', 423: 'Locked', 424: 'Failed Dependency', 426: 'Upgrade Required', 500: 'Internal Server Error', 501: 'Not Implemented', 502: 'Bad Gateway', 503: 'Service Unavailable', 504: 'Gateway Timeout', 505: 'HTTP Version Not Supported', 506: 'Variant Also Negotiates', 507: 'Insufficient Storage', 510: 'Not Extended' }; function XMLHttpRequestUpload() { this.uid = Basic.guid('uid_'); } XMLHttpRequestUpload.prototype = EventTarget.instance; /** Implementation of XMLHttpRequest @class XMLHttpRequest @constructor @uses RuntimeClient @extends EventTarget */ var dispatches = [ 'loadstart', 'progress', 'abort', 'error', 'load', 'timeout', 'loadend' // readystatechange (for historical reasons) ]; var NATIVE = 1, RUNTIME = 2; function XMLHttpRequest() { var self = this, // this (together with _p() @see below) is here to gracefully upgrade to setter/getter syntax where possible props = { /** The amount of milliseconds a request can take before being terminated. Initially zero. Zero means there is no timeout. @property timeout @type Number @default 0 */ timeout: 0, /** Current state, can take following values: UNSENT (numeric value 0) The object has been constructed. OPENED (numeric value 1) The open() method has been successfully invoked. During this state request headers can be set using setRequestHeader() and the request can be made using the send() method. HEADERS_RECEIVED (numeric value 2) All redirects (if any) have been followed and all HTTP headers of the final response have been received. Several response members of the object are now available. LOADING (numeric value 3) The response entity body is being received. DONE (numeric value 4) @property readyState @type Number @default 0 (UNSENT) */ readyState: XMLHttpRequest.UNSENT, /** True when user credentials are to be included in a cross-origin request. False when they are to be excluded in a cross-origin request and when cookies are to be ignored in its response. Initially false. @property withCredentials @type Boolean @default false */ withCredentials: false, /** Returns the HTTP status code. @property status @type Number @default 0 */ status: 0, /** Returns the HTTP status text. @property statusText @type String */ statusText: "", /** Returns the response type. Can be set to change the response type. Values are: the empty string (default), "arraybuffer", "blob", "document", "json", and "text". @property responseType @type String */ responseType: "", /** Returns the document response entity body. Throws an "InvalidStateError" exception if responseType is not the empty string or "document". @property responseXML @type Document */ responseXML: null, /** Returns the text response entity body. Throws an "InvalidStateError" exception if responseType is not the empty string or "text". @property responseText @type String */ responseText: null, /** Returns the response entity body (http://www.w3.org/TR/XMLHttpRequest/#response-entity-body). Can become: ArrayBuffer, Blob, Document, JSON, Text @property response @type Mixed */ response: null }, _async = true, _url, _method, _headers = {}, _user, _password, _encoding = null, _mimeType = null, // flags _sync_flag = false, _send_flag = false, _upload_events_flag = false, _upload_complete_flag = false, _error_flag = false, _same_origin_flag = false, // times _start_time, _timeoutset_time, _finalMime = null, _finalCharset = null, _options = {}, _xhr, _responseHeaders = '', _responseHeadersBag ; Basic.extend(this, props, { /** Unique id of the component @property uid @type String */ uid: Basic.guid('uid_'), /** Target for Upload events @property upload @type XMLHttpRequestUpload */ upload: new XMLHttpRequestUpload(), /** Sets the request method, request URL, synchronous flag, request username, and request password. Throws a "SyntaxError" exception if one of the following is true: method is not a valid HTTP method. url cannot be resolved. url contains the "user:password" format in the userinfo production. Throws a "SecurityError" exception if method is a case-insensitive match for CONNECT, TRACE or TRACK. Throws an "InvalidAccessError" exception if one of the following is true: Either user or password is passed as argument and the origin of url does not match the XMLHttpRequest origin. There is an associated XMLHttpRequest document and either the timeout attribute is not zero, the withCredentials attribute is true, or the responseType attribute is not the empty string. @method open @param {String} method HTTP method to use on request @param {String} url URL to request @param {Boolean} [async=true] If false request will be done in synchronous manner. Asynchronous by default. @param {String} [user] Username to use in HTTP authentication process on server-side @param {String} [password] Password to use in HTTP authentication process on server-side */ open: function(method, url, async, user, password) { var urlp; // first two arguments are required if (!method || !url) { throw new x.DOMException(x.DOMException.SYNTAX_ERR); } // 2 - check if any code point in method is higher than U+00FF or after deflating method it does not match the method if (/[\u0100-\uffff]/.test(method) || Encode.utf8_encode(method) !== method) { throw new x.DOMException(x.DOMException.SYNTAX_ERR); } // 3 if (!!~Basic.inArray(method.toUpperCase(), ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'TRACE', 'TRACK'])) { _method = method.toUpperCase(); } // 4 - allowing these methods poses a security risk if (!!~Basic.inArray(_method, ['CONNECT', 'TRACE', 'TRACK'])) { throw new x.DOMException(x.DOMException.SECURITY_ERR); } // 5 url = Encode.utf8_encode(url); // 6 - Resolve url relative to the XMLHttpRequest base URL. If the algorithm returns an error, throw a "SyntaxError". urlp = Url.parseUrl(url); _same_origin_flag = Url.hasSameOrigin(urlp); // 7 - manually build up absolute url _url = Url.resolveUrl(url); // 9-10, 12-13 if ((user || password) && !_same_origin_flag) { throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); } _user = user || urlp.user; _password = password || urlp.pass; // 11 _async = async || true; if (_async === false && (_p('timeout') || _p('withCredentials') || _p('responseType') !== "")) { throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); } // 14 - terminate abort() // 15 - terminate send() // 18 _sync_flag = !_async; _send_flag = false; _headers = {}; _reset.call(this); // 19 _p('readyState', XMLHttpRequest.OPENED); // 20 this.dispatchEvent('readystatechange'); }, /** Appends an header to the list of author request headers, or if header is already in the list of author request headers, combines its value with value. Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set. Throws a "SyntaxError" exception if header is not a valid HTTP header field name or if value is not a valid HTTP header field value. @method setRequestHeader @param {String} header @param {String|Number} value */ setRequestHeader: function(header, value) { var uaHeaders = [ // these headers are controlled by the user agent "accept-charset", "accept-encoding", "access-control-request-headers", "access-control-request-method", "connection", "content-length", "cookie", "cookie2", "content-transfer-encoding", "date", "expect", "host", "keep-alive", "origin", "referer", "te", "trailer", "transfer-encoding", "upgrade", "user-agent", "via" ]; // 1-2 if (_p('readyState') !== XMLHttpRequest.OPENED || _send_flag) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } // 3 if (/[\u0100-\uffff]/.test(header) || Encode.utf8_encode(header) !== header) { throw new x.DOMException(x.DOMException.SYNTAX_ERR); } // 4 /* this step is seemingly bypassed in browsers, probably to allow various unicode characters in header values if (/[\u0100-\uffff]/.test(value) || Encode.utf8_encode(value) !== value) { throw new x.DOMException(x.DOMException.SYNTAX_ERR); }*/ header = Basic.trim(header).toLowerCase(); // setting of proxy-* and sec-* headers is prohibited by spec if (!!~Basic.inArray(header, uaHeaders) || /^(proxy\-|sec\-)/.test(header)) { return false; } // camelize // browsers lowercase header names (at least for custom ones) // header = header.replace(/\b\w/g, function($1) { return $1.toUpperCase(); }); if (!_headers[header]) { _headers[header] = value; } else { // http://tools.ietf.org/html/rfc2616#section-4.2 (last paragraph) _headers[header] += ', ' + value; } return true; }, /** Returns all headers from the response, with the exception of those whose field name is Set-Cookie or Set-Cookie2. @method getAllResponseHeaders @return {String} reponse headers or empty string */ getAllResponseHeaders: function() { return _responseHeaders || ''; }, /** Returns the header field value from the response of which the field name matches header, unless the field name is Set-Cookie or Set-Cookie2. @method getResponseHeader @param {String} header @return {String} value(s) for the specified header or null */ getResponseHeader: function(header) { header = header.toLowerCase(); if (_error_flag || !!~Basic.inArray(header, ['set-cookie', 'set-cookie2'])) { return null; } if (_responseHeaders && _responseHeaders !== '') { // if we didn't parse response headers until now, do it and keep for later if (!_responseHeadersBag) { _responseHeadersBag = {}; Basic.each(_responseHeaders.split(/\r\n/), function(line) { var pair = line.split(/:\s+/); if (pair.length === 2) { // last line might be empty, omit pair[0] = Basic.trim(pair[0]); // just in case _responseHeadersBag[pair[0].toLowerCase()] = { // simply to retain header name in original form header: pair[0], value: Basic.trim(pair[1]) }; } }); } if (_responseHeadersBag.hasOwnProperty(header)) { return _responseHeadersBag[header].header + ': ' + _responseHeadersBag[header].value; } } return null; }, /** Sets the Content-Type header for the response to mime. Throws an "InvalidStateError" exception if the state is LOADING or DONE. Throws a "SyntaxError" exception if mime is not a valid media type. @method overrideMimeType @param String mime Mime type to set */ overrideMimeType: function(mime) { var matches, charset; // 1 if (!!~Basic.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } // 2 mime = Basic.trim(mime.toLowerCase()); if (/;/.test(mime) && (matches = mime.match(/^([^;]+)(?:;\scharset\=)?(.*)$/))) { mime = matches[1]; if (matches[2]) { charset = matches[2]; } } if (!Mime.mimes[mime]) { throw new x.DOMException(x.DOMException.SYNTAX_ERR); } // 3-4 _finalMime = mime; _finalCharset = charset; }, /** Initiates the request. The optional argument provides the request entity body. The argument is ignored if request method is GET or HEAD. Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set. @method send @param {Blob|Document|String|FormData} [data] Request entity body @param {Object} [options] Set of requirements and pre-requisities for runtime initialization */ send: function(data, options) { if (Basic.typeOf(options) === 'string') { _options = { ruid: options }; } else if (!options) { _options = {}; } else { _options = options; } // 1-2 if (this.readyState !== XMLHttpRequest.OPENED || _send_flag) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } // 3 // sending Blob if (data instanceof Blob) { _options.ruid = data.ruid; _mimeType = data.type || 'application/octet-stream'; } // FormData else if (data instanceof FormData) { if (data.hasBlob()) { var blob = data.getBlob(); _options.ruid = blob.ruid; _mimeType = blob.type || 'application/octet-stream'; } } // DOMString else if (typeof data === 'string') { _encoding = 'UTF-8'; _mimeType = 'text/plain;charset=UTF-8'; // data should be converted to Unicode and encoded as UTF-8 data = Encode.utf8_encode(data); } // if withCredentials not set, but requested, set it automatically if (!this.withCredentials) { this.withCredentials = (_options.required_caps && _options.required_caps.send_browser_cookies) && !_same_origin_flag; } // 4 - storage mutex // 5 _upload_events_flag = (!_sync_flag && this.upload.hasEventListener()); // DSAP // 6 _error_flag = false; // 7 _upload_complete_flag = !data; // 8 - Asynchronous steps if (!_sync_flag) { // 8.1 _send_flag = true; // 8.2 // this.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr // 8.3 //if (!_upload_complete_flag) { // this.upload.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr //} } // 8.5 - Return the send() method call, but continue running the steps in this algorithm. _doXHR.call(this, data); }, /** Cancels any network activity. @method abort */ abort: function() { _error_flag = true; _sync_flag = false; if (!~Basic.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED, XMLHttpRequest.DONE])) { _p('readyState', XMLHttpRequest.DONE); _send_flag = false; if (_xhr) { _xhr.getRuntime().exec.call(_xhr, 'XMLHttpRequest', 'abort', _upload_complete_flag); } else { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } _upload_complete_flag = true; } else { _p('readyState', XMLHttpRequest.UNSENT); } }, destroy: function() { if (_xhr) { if (Basic.typeOf(_xhr.destroy) === 'function') { _xhr.destroy(); } _xhr = null; } this.unbindAll(); if (this.upload) { this.upload.unbindAll(); this.upload = null; } } }); this.handleEventProps(dispatches.concat(['readystatechange'])); // for historical reasons this.upload.handleEventProps(dispatches); /* this is nice, but maybe too lengthy // if supported by JS version, set getters/setters for specific properties o.defineProperty(this, 'readyState', { configurable: false, get: function() { return _p('readyState'); } }); o.defineProperty(this, 'timeout', { configurable: false, get: function() { return _p('timeout'); }, set: function(value) { if (_sync_flag) { throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); } // timeout still should be measured relative to the start time of request _timeoutset_time = (new Date).getTime(); _p('timeout', value); } }); // the withCredentials attribute has no effect when fetching same-origin resources o.defineProperty(this, 'withCredentials', { configurable: false, get: function() { return _p('withCredentials'); }, set: function(value) { // 1-2 if (!~o.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED]) || _send_flag) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } // 3-4 if (_anonymous_flag || _sync_flag) { throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); } // 5 _p('withCredentials', value); } }); o.defineProperty(this, 'status', { configurable: false, get: function() { return _p('status'); } }); o.defineProperty(this, 'statusText', { configurable: false, get: function() { return _p('statusText'); } }); o.defineProperty(this, 'responseType', { configurable: false, get: function() { return _p('responseType'); }, set: function(value) { // 1 if (!!~o.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } // 2 if (_sync_flag) { throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); } // 3 _p('responseType', value.toLowerCase()); } }); o.defineProperty(this, 'responseText', { configurable: false, get: function() { // 1 if (!~o.inArray(_p('responseType'), ['', 'text'])) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } // 2-3 if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } return _p('responseText'); } }); o.defineProperty(this, 'responseXML', { configurable: false, get: function() { // 1 if (!~o.inArray(_p('responseType'), ['', 'document'])) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } // 2-3 if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } return _p('responseXML'); } }); o.defineProperty(this, 'response', { configurable: false, get: function() { if (!!~o.inArray(_p('responseType'), ['', 'text'])) { if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) { return ''; } } if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) { return null; } return _p('response'); } }); */ function _p(prop, value) { if (!props.hasOwnProperty(prop)) { return; } if (arguments.length === 1) { // get return Env.can('define_property') ? props[prop] : self[prop]; } else { // set if (Env.can('define_property')) { props[prop] = value; } else { self[prop] = value; } } } /* function _toASCII(str, AllowUnassigned, UseSTD3ASCIIRules) { // TODO: http://tools.ietf.org/html/rfc3490#section-4.1 return str.toLowerCase(); } */ function _doXHR(data) { var self = this; _start_time = new Date().getTime(); _xhr = new RuntimeTarget(); function loadEnd() { if (_xhr) { // it could have been destroyed by now _xhr.destroy(); _xhr = null; } self.dispatchEvent('loadend'); self = null; } function exec(runtime) { _xhr.bind('LoadStart', function(e) { _p('readyState', XMLHttpRequest.LOADING); self.dispatchEvent('readystatechange'); self.dispatchEvent(e); if (_upload_events_flag) { self.upload.dispatchEvent(e); } }); _xhr.bind('Progress', function(e) { if (_p('readyState') !== XMLHttpRequest.LOADING) { _p('readyState', XMLHttpRequest.LOADING); // LoadStart unreliable (in Flash for example) self.dispatchEvent('readystatechange'); } self.dispatchEvent(e); }); _xhr.bind('UploadProgress', function(e) { if (_upload_events_flag) { self.upload.dispatchEvent({ type: 'progress', lengthComputable: false, total: e.total, loaded: e.loaded }); } }); _xhr.bind('Load', function(e) { _p('readyState', XMLHttpRequest.DONE); _p('status', Number(runtime.exec.call(_xhr, 'XMLHttpRequest', 'getStatus') || 0)); _p('statusText', httpCode[_p('status')] || ""); _p('response', runtime.exec.call(_xhr, 'XMLHttpRequest', 'getResponse', _p('responseType'))); if (!!~Basic.inArray(_p('responseType'), ['text', ''])) { _p('responseText', _p('response')); } else if (_p('responseType') === 'document') { _p('responseXML', _p('response')); } _responseHeaders = runtime.exec.call(_xhr, 'XMLHttpRequest', 'getAllResponseHeaders'); self.dispatchEvent('readystatechange'); if (_p('status') > 0) { // status 0 usually means that server is unreachable if (_upload_events_flag) { self.upload.dispatchEvent(e); } self.dispatchEvent(e); } else { _error_flag = true; self.dispatchEvent('error'); } loadEnd(); }); _xhr.bind('Abort', function(e) { self.dispatchEvent(e); loadEnd(); }); _xhr.bind('Error', function(e) { _error_flag = true; _p('readyState', XMLHttpRequest.DONE); self.dispatchEvent('readystatechange'); _upload_complete_flag = true; self.dispatchEvent(e); loadEnd(); }); runtime.exec.call(_xhr, 'XMLHttpRequest', 'send', { url: _url, method: _method, async: _async, user: _user, password: _password, headers: _headers, mimeType: _mimeType, encoding: _encoding, responseType: self.responseType, withCredentials: self.withCredentials, options: _options }, data); } // clarify our requirements if (typeof(_options.required_caps) === 'string') { _options.required_caps = Runtime.parseCaps(_options.required_caps); } _options.required_caps = Basic.extend({}, _options.required_caps, { return_response_type: self.responseType }); if (data instanceof FormData) { _options.required_caps.send_multipart = true; } if (!Basic.isEmptyObj(_headers)) { _options.required_caps.send_custom_headers = true; } if (!_same_origin_flag) { _options.required_caps.do_cors = true; } if (_options.ruid) { // we do not need to wait if we can connect directly exec(_xhr.connectRuntime(_options)); } else { _xhr.bind('RuntimeInit', function(e, runtime) { exec(runtime); }); _xhr.bind('RuntimeError', function(e, err) { self.dispatchEvent('RuntimeError', err); }); _xhr.connectRuntime(_options); } } function _reset() { _p('responseText', ""); _p('responseXML', null); _p('response', null); _p('status', 0); _p('statusText', ""); _start_time = _timeoutset_time = null; } } XMLHttpRequest.UNSENT = 0; XMLHttpRequest.OPENED = 1; XMLHttpRequest.HEADERS_RECEIVED = 2; XMLHttpRequest.LOADING = 3; XMLHttpRequest.DONE = 4; XMLHttpRequest.prototype = EventTarget.instance; return XMLHttpRequest; }); // Included from: src/javascript/runtime/Transporter.js /** * Transporter.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define("moxie/runtime/Transporter", [ "moxie/core/utils/Basic", "moxie/core/utils/Encode", "moxie/runtime/RuntimeClient", "moxie/core/EventTarget" ], function(Basic, Encode, RuntimeClient, EventTarget) { function Transporter() { var mod, _runtime, _data, _size, _pos, _chunk_size; RuntimeClient.call(this); Basic.extend(this, { uid: Basic.guid('uid_'), state: Transporter.IDLE, result: null, transport: function(data, type, options) { var self = this; options = Basic.extend({ chunk_size: 204798 }, options); // should divide by three, base64 requires this if ((mod = options.chunk_size % 3)) { options.chunk_size += 3 - mod; } _chunk_size = options.chunk_size; _reset.call(this); _data = data; _size = data.length; if (Basic.typeOf(options) === 'string' || options.ruid) { _run.call(self, type, this.connectRuntime(options)); } else { // we require this to run only once var cb = function(e, runtime) { self.unbind("RuntimeInit", cb); _run.call(self, type, runtime); }; this.bind("RuntimeInit", cb); this.connectRuntime(options); } }, abort: function() { var self = this; self.state = Transporter.IDLE; if (_runtime) { _runtime.exec.call(self, 'Transporter', 'clear'); self.trigger("TransportingAborted"); } _reset.call(self); }, destroy: function() { this.unbindAll(); _runtime = null; this.disconnectRuntime(); _reset.call(this); } }); function _reset() { _size = _pos = 0; _data = this.result = null; } function _run(type, runtime) { var self = this; _runtime = runtime; //self.unbind("RuntimeInit"); self.bind("TransportingProgress", function(e) { _pos = e.loaded; if (_pos < _size && Basic.inArray(self.state, [Transporter.IDLE, Transporter.DONE]) === -1) { _transport.call(self); } }, 999); self.bind("TransportingComplete", function() { _pos = _size; self.state = Transporter.DONE; _data = null; // clean a bit self.result = _runtime.exec.call(self, 'Transporter', 'getAsBlob', type || ''); }, 999); self.state = Transporter.BUSY; self.trigger("TransportingStarted"); _transport.call(self); } function _transport() { var self = this, chunk, bytesLeft = _size - _pos; if (_chunk_size > bytesLeft) { _chunk_size = bytesLeft; } chunk = Encode.btoa(_data.substr(_pos, _chunk_size)); _runtime.exec.call(self, 'Transporter', 'receive', chunk, _size); } } Transporter.IDLE = 0; Transporter.BUSY = 1; Transporter.DONE = 2; Transporter.prototype = EventTarget.instance; return Transporter; }); // Included from: src/javascript/image/Image.js /** * Image.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define("moxie/image/Image", [ "moxie/core/utils/Basic", "moxie/core/utils/Dom", "moxie/core/Exceptions", "moxie/file/FileReaderSync", "moxie/xhr/XMLHttpRequest", "moxie/runtime/Runtime", "moxie/runtime/RuntimeClient", "moxie/runtime/Transporter", "moxie/core/utils/Env", "moxie/core/EventTarget", "moxie/file/Blob", "moxie/file/File", "moxie/core/utils/Encode" ], function(Basic, Dom, x, FileReaderSync, XMLHttpRequest, Runtime, RuntimeClient, Transporter, Env, EventTarget, Blob, File, Encode) { /** Image preloading and manipulation utility. Additionally it provides access to image meta info (Exif, GPS) and raw binary data. @class Image @constructor @extends EventTarget */ var dispatches = [ 'progress', /** Dispatched when loading is complete. @event load @param {Object} event */ 'load', 'error', /** Dispatched when resize operation is complete. @event resize @param {Object} event */ 'resize', /** Dispatched when visual representation of the image is successfully embedded into the corresponsing container. @event embedded @param {Object} event */ 'embedded' ]; function Image() { RuntimeClient.call(this); Basic.extend(this, { /** Unique id of the component @property uid @type {String} */ uid: Basic.guid('uid_'), /** Unique id of the connected runtime, if any. @property ruid @type {String} */ ruid: null, /** Name of the file, that was used to create an image, if available. If not equals to empty string. @property name @type {String} @default "" */ name: "", /** Size of the image in bytes. Actual value is set only after image is preloaded. @property size @type {Number} @default 0 */ size: 0, /** Width of the image. Actual value is set only after image is preloaded. @property width @type {Number} @default 0 */ width: 0, /** Height of the image. Actual value is set only after image is preloaded. @property height @type {Number} @default 0 */ height: 0, /** Mime type of the image. Currently only image/jpeg and image/png are supported. Actual value is set only after image is preloaded. @property type @type {String} @default "" */ type: "", /** Holds meta info (Exif, GPS). Is populated only for image/jpeg. Actual value is set only after image is preloaded. @property meta @type {Object} @default {} */ meta: {}, /** Alias for load method, that takes another mOxie.Image object as a source (see load). @method clone @param {Image} src Source for the image @param {Boolean} [exact=false] Whether to activate in-depth clone mode */ clone: function() { this.load.apply(this, arguments); }, /** Loads image from various sources. Currently the source for new image can be: mOxie.Image, mOxie.Blob/mOxie.File, native Blob/File, dataUrl or URL. Depending on the type of the source, arguments - differ. When source is URL, Image will be downloaded from remote destination and loaded in memory. @example var img = new mOxie.Image(); img.onload = function() { var blob = img.getAsBlob(); var formData = new mOxie.FormData(); formData.append('file', blob); var xhr = new mOxie.XMLHttpRequest(); xhr.onload = function() { // upload complete }; xhr.open('post', 'upload.php'); xhr.send(formData); }; img.load("http://www.moxiecode.com/images/mox-logo.jpg"); // notice file extension (.jpg) @method load @param {Image|Blob|File|String} src Source for the image @param {Boolean|Object} [mixed] */ load: function() { _load.apply(this, arguments); }, /** Downsizes the image to fit the specified width/height. If crop is supplied, image will be cropped to exact dimensions. @method downsize @param {Object} opts @param {Number} opts.width Resulting width @param {Number} [opts.height=width] Resulting height (optional, if not supplied will default to width) @param {Boolean} [opts.crop=false] Whether to crop the image to exact dimensions @param {Boolean} [opts.preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize) @param {String} [opts.resample=false] Resampling algorithm to use for resizing */ downsize: function(opts) { var defaults = { width: this.width, height: this.height, type: this.type || 'image/jpeg', quality: 90, crop: false, preserveHeaders: true, resample: false }; if (typeof(opts) === 'object') { opts = Basic.extend(defaults, opts); } else { // for backward compatibility opts = Basic.extend(defaults, { width: arguments[0], height: arguments[1], crop: arguments[2], preserveHeaders: arguments[3] }); } try { if (!this.size) { // only preloaded image objects can be used as source throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } // no way to reliably intercept the crash due to high resolution, so we simply avoid it if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) { throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR); } this.exec('Image', 'downsize', opts.width, opts.height, opts.crop, opts.preserveHeaders); } catch(ex) { // for now simply trigger error event this.trigger('error', ex.code); } }, /** Alias for downsize(width, height, true). (see downsize) @method crop @param {Number} width Resulting width @param {Number} [height=width] Resulting height (optional, if not supplied will default to width) @param {Boolean} [preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize) */ crop: function(width, height, preserveHeaders) { this.downsize(width, height, true, preserveHeaders); }, getAsCanvas: function() { if (!Env.can('create_canvas')) { throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR); } var runtime = this.connectRuntime(this.ruid); return runtime.exec.call(this, 'Image', 'getAsCanvas'); }, /** Retrieves image in it's current state as mOxie.Blob object. Cannot be run on empty or image in progress (throws DOMException.INVALID_STATE_ERR). @method getAsBlob @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png @param {Number} [quality=90] Applicable only together with mime type image/jpeg @return {Blob} Image as Blob */ getAsBlob: function(type, quality) { if (!this.size) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } return this.exec('Image', 'getAsBlob', type || 'image/jpeg', quality || 90); }, /** Retrieves image in it's current state as dataURL string. Cannot be run on empty or image in progress (throws DOMException.INVALID_STATE_ERR). @method getAsDataURL @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png @param {Number} [quality=90] Applicable only together with mime type image/jpeg @return {String} Image as dataURL string */ getAsDataURL: function(type, quality) { if (!this.size) { throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } return this.exec('Image', 'getAsDataURL', type || 'image/jpeg', quality || 90); }, /** Retrieves image in it's current state as binary string. Cannot be run on empty or image in progress (throws DOMException.INVALID_STATE_ERR). @method getAsBinaryString @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png @param {Number} [quality=90] Applicable only together with mime type image/jpeg @return {String} Image as binary string */ getAsBinaryString: function(type, quality) { var dataUrl = this.getAsDataURL(type, quality); return Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7)); }, /** Embeds a visual representation of the image into the specified node. Depending on the runtime, it might be a canvas, an img node or a thrid party shim object (Flash or SilverLight - very rare, can be used in legacy browsers that do not have canvas or proper dataURI support). @method embed @param {DOMElement} el DOM element to insert the image object into @param {Object} [opts] @param {Number} [opts.width] The width of an embed (defaults to the image width) @param {Number} [opts.height] The height of an embed (defaults to the image height) @param {String} [type="image/jpeg"] Mime type @param {Number} [quality=90] Quality of an embed, if mime type is image/jpeg @param {Boolean} [crop=false] Whether to crop an embed to the specified dimensions */ embed: function(el, opts) { var self = this , runtime // this has to be outside of all the closures to contain proper runtime ; opts = Basic.extend({ width: this.width, height: this.height, type: this.type || 'image/jpeg', quality: 90 }, opts || {}); function render(type, quality) { var img = this; // if possible, embed a canvas element directly if (Env.can('create_canvas')) { var canvas = img.getAsCanvas(); if (canvas) { el.appendChild(canvas); canvas = null; img.destroy(); self.trigger('embedded'); return; } } var dataUrl = img.getAsDataURL(type, quality); if (!dataUrl) { throw new x.ImageError(x.ImageError.WRONG_FORMAT); } if (Env.can('use_data_uri_of', dataUrl.length)) { el.innerHTML = ''; img.destroy(); self.trigger('embedded'); } else { var tr = new Transporter(); tr.bind("TransportingComplete", function() { runtime = self.connectRuntime(this.result.ruid); self.bind("Embedded", function() { // position and size properly Basic.extend(runtime.getShimContainer().style, { //position: 'relative', top: '0px', left: '0px', width: img.width + 'px', height: img.height + 'px' }); // some shims (Flash/SilverLight) reinitialize, if parent element is hidden, reordered or it's // position type changes (in Gecko), but since we basically need this only in IEs 6/7 and // sometimes 8 and they do not have this problem, we can comment this for now /*tr.bind("RuntimeInit", function(e, runtime) { tr.destroy(); runtime.destroy(); onResize.call(self); // re-feed our image data });*/ runtime = null; // release }, 999); runtime.exec.call(self, "ImageView", "display", this.result.uid, width, height); img.destroy(); }); tr.transport(Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7)), type, { required_caps: { display_media: true }, runtime_order: 'flash,silverlight', container: el }); } } try { if (!(el = Dom.get(el))) { throw new x.DOMException(x.DOMException.INVALID_NODE_TYPE_ERR); } if (!this.size) { // only preloaded image objects can be used as source throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } // high-resolution images cannot be consistently handled across the runtimes if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) { //throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR); } var imgCopy = new Image(); imgCopy.bind("Resize", function() { render.call(this, opts.type, opts.quality); }); imgCopy.bind("Load", function() { imgCopy.downsize(opts); }); // if embedded thumb data is available and dimensions are big enough, use it if (this.meta.thumb && this.meta.thumb.width >= opts.width && this.meta.thumb.height >= opts.height) { imgCopy.load(this.meta.thumb.data); } else { imgCopy.clone(this, false); } return imgCopy; } catch(ex) { // for now simply trigger error event this.trigger('error', ex.code); } }, /** Properly destroys the image and frees resources in use. If any. Recommended way to dispose mOxie.Image object. @method destroy */ destroy: function() { if (this.ruid) { this.getRuntime().exec.call(this, 'Image', 'destroy'); this.disconnectRuntime(); } this.unbindAll(); } }); // this is here, because in order to bind properly, we need uid, which is created above this.handleEventProps(dispatches); this.bind('Load Resize', function() { _updateInfo.call(this); }, 999); function _updateInfo(info) { if (!info) { info = this.exec('Image', 'getInfo'); } this.size = info.size; this.width = info.width; this.height = info.height; this.type = info.type; this.meta = info.meta; // update file name, only if empty if (this.name === '') { this.name = info.name; } } function _load(src) { var srcType = Basic.typeOf(src); try { // if source is Image if (src instanceof Image) { if (!src.size) { // only preloaded image objects can be used as source throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); } _loadFromImage.apply(this, arguments); } // if source is o.Blob/o.File else if (src instanceof Blob) { if (!~Basic.inArray(src.type, ['image/jpeg', 'image/png'])) { throw new x.ImageError(x.ImageError.WRONG_FORMAT); } _loadFromBlob.apply(this, arguments); } // if native blob/file else if (Basic.inArray(srcType, ['blob', 'file']) !== -1) { _load.call(this, new File(null, src), arguments[1]); } // if String else if (srcType === 'string') { // if dataUrl String if (src.substr(0, 5) === 'data:') { _load.call(this, new Blob(null, { data: src }), arguments[1]); } // else assume Url, either relative or absolute else { _loadFromUrl.apply(this, arguments); } } // if source seems to be an img node else if (srcType === 'node' && src.nodeName.toLowerCase() === 'img') { _load.call(this, src.src, arguments[1]); } else { throw new x.DOMException(x.DOMException.TYPE_MISMATCH_ERR); } } catch(ex) { // for now simply trigger error event this.trigger('error', ex.code); } } function _loadFromImage(img, exact) { var runtime = this.connectRuntime(img.ruid); this.ruid = runtime.uid; runtime.exec.call(this, 'Image', 'loadFromImage', img, (Basic.typeOf(exact) === 'undefined' ? true : exact)); } function _loadFromBlob(blob, options) { var self = this; self.name = blob.name || ''; function exec(runtime) { self.ruid = runtime.uid; runtime.exec.call(self, 'Image', 'loadFromBlob', blob); } if (blob.isDetached()) { this.bind('RuntimeInit', function(e, runtime) { exec(runtime); }); // convert to object representation if (options && typeof(options.required_caps) === 'string') { options.required_caps = Runtime.parseCaps(options.required_caps); } this.connectRuntime(Basic.extend({ required_caps: { access_image_binary: true, resize_image: true } }, options)); } else { exec(this.connectRuntime(blob.ruid)); } } function _loadFromUrl(url, options) { var self = this, xhr; xhr = new XMLHttpRequest(); xhr.open('get', url); xhr.responseType = 'blob'; xhr.onprogress = function(e) { self.trigger(e); }; xhr.onload = function() { _loadFromBlob.call(self, xhr.response, true); }; xhr.onerror = function(e) { self.trigger(e); }; xhr.onloadend = function() { xhr.destroy(); }; xhr.bind('RuntimeError', function(e, err) { self.trigger('RuntimeError', err); }); xhr.send(null, options); } } // virtual world will crash on you if image has a resolution higher than this: Image.MAX_RESIZE_WIDTH = 8192; Image.MAX_RESIZE_HEIGHT = 8192; Image.prototype = EventTarget.instance; return Image; }); // Included from: src/javascript/runtime/html5/Runtime.js /** * Runtime.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /*global File:true */ /** Defines constructor for HTML5 runtime. @class moxie/runtime/html5/Runtime @private */ define("moxie/runtime/html5/Runtime", [ "moxie/core/utils/Basic", "moxie/core/Exceptions", "moxie/runtime/Runtime", "moxie/core/utils/Env" ], function(Basic, x, Runtime, Env) { var type = "html5", extensions = {}; function Html5Runtime(options) { var I = this , Test = Runtime.capTest , True = Runtime.capTrue ; var caps = Basic.extend({ access_binary: Test(window.FileReader || window.File && window.File.getAsDataURL), access_image_binary: function() { return I.can('access_binary') && !!extensions.Image; }, display_media: Test(Env.can('create_canvas') || Env.can('use_data_uri_over32kb')), do_cors: Test(window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()), drag_and_drop: Test(function() { // this comes directly from Modernizr: http://www.modernizr.com/ var div = document.createElement('div'); // IE has support for drag and drop since version 5, but doesn't support dropping files from desktop return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && (Env.browser !== 'IE' || Env.verComp(Env.version, 9, '>')); }()), filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest return (Env.browser === 'Chrome' && Env.verComp(Env.version, 28, '>=')) || (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) || (Env.browser === 'Safari' && Env.verComp(Env.version, 7, '>=')); }()), return_response_headers: True, return_response_type: function(responseType) { if (responseType === 'json' && !!window.JSON) { // we can fake this one even if it's not supported return true; } return Env.can('return_response_type', responseType); }, return_status_code: True, report_upload_progress: Test(window.XMLHttpRequest && new XMLHttpRequest().upload), resize_image: function() { return I.can('access_binary') && Env.can('create_canvas'); }, select_file: function() { return Env.can('use_fileinput') && window.File; }, select_folder: function() { return I.can('select_file') && Env.browser === 'Chrome' && Env.verComp(Env.version, 21, '>='); }, select_multiple: function() { // it is buggy on Safari Windows and iOS return I.can('select_file') && !(Env.browser === 'Safari' && Env.os === 'Windows') && !(Env.os === 'iOS' && Env.verComp(Env.osVersion, "7.0.0", '>') && Env.verComp(Env.osVersion, "8.0.0", '<')); }, send_binary_string: Test(window.XMLHttpRequest && (new XMLHttpRequest().sendAsBinary || (window.Uint8Array && window.ArrayBuffer))), send_custom_headers: Test(window.XMLHttpRequest), send_multipart: function() { return !!(window.XMLHttpRequest && new XMLHttpRequest().upload && window.FormData) || I.can('send_binary_string'); }, slice_blob: Test(window.File && (File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice)), stream_upload: function(){ return I.can('slice_blob') && I.can('send_multipart'); }, summon_file_dialog: function() { // yeah... some dirty sniffing here... return I.can('select_file') && ( (Env.browser === 'Firefox' && Env.verComp(Env.version, 4, '>=')) || (Env.browser === 'Opera' && Env.verComp(Env.version, 12, '>=')) || (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) || !!~Basic.inArray(Env.browser, ['Chrome', 'Safari']) ); }, upload_filesize: True }, arguments[2] ); Runtime.call(this, options, (arguments[1] || type), caps); Basic.extend(this, { init : function() { this.trigger("Init"); }, destroy: (function(destroy) { // extend default destroy method return function() { destroy.call(I); destroy = I = null; }; }(this.destroy)) }); Basic.extend(this.getShim(), extensions); } Runtime.addConstructor(type, Html5Runtime); return extensions; }); // Included from: src/javascript/core/utils/Events.js /** * Events.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ define('moxie/core/utils/Events', [ 'moxie/core/utils/Basic' ], function(Basic) { var eventhash = {}, uid = 'moxie_' + Basic.guid(); // IE W3C like event funcs function preventDefault() { this.returnValue = false; } function stopPropagation() { this.cancelBubble = true; } /** Adds an event handler to the specified object and store reference to the handler in objects internal Plupload registry (@see removeEvent). @method addEvent @for Utils @static @param {Object} obj DOM element like object to add handler to. @param {String} name Name to add event listener to. @param {Function} callback Function to call when event occurs. @param {String} [key] that might be used to add specifity to the event record. */ var addEvent = function(obj, name, callback, key) { var func, events; name = name.toLowerCase(); // Add event listener if (obj.addEventListener) { func = callback; obj.addEventListener(name, func, false); } else if (obj.attachEvent) { func = function() { var evt = window.event; if (!evt.target) { evt.target = evt.srcElement; } evt.preventDefault = preventDefault; evt.stopPropagation = stopPropagation; callback(evt); }; obj.attachEvent('on' + name, func); } // Log event handler to objects internal mOxie registry if (!obj[uid]) { obj[uid] = Basic.guid(); } if (!eventhash.hasOwnProperty(obj[uid])) { eventhash[obj[uid]] = {}; } events = eventhash[obj[uid]]; if (!events.hasOwnProperty(name)) { events[name] = []; } events[name].push({ func: func, orig: callback, // store original callback for IE key: key }); }; /** Remove event handler from the specified object. If third argument (callback) is not specified remove all events with the specified name. @method removeEvent @static @param {Object} obj DOM element to remove event listener(s) from. @param {String} name Name of event listener to remove. @param {Function|String} [callback] might be a callback or unique key to match. */ var removeEvent = function(obj, name, callback) { var type, undef; name = name.toLowerCase(); if (obj[uid] && eventhash[obj[uid]] && eventhash[obj[uid]][name]) { type = eventhash[obj[uid]][name]; } else { return; } for (var i = type.length - 1; i >= 0; i--) { // undefined or not, key should match if (type[i].orig === callback || type[i].key === callback) { if (obj.removeEventListener) { obj.removeEventListener(name, type[i].func, false); } else if (obj.detachEvent) { obj.detachEvent('on'+name, type[i].func); } type[i].orig = null; type[i].func = null; type.splice(i, 1); // If callback was passed we are done here, otherwise proceed if (callback !== undef) { break; } } } // If event array got empty, remove it if (!type.length) { delete eventhash[obj[uid]][name]; } // If mOxie registry has become empty, remove it if (Basic.isEmptyObj(eventhash[obj[uid]])) { delete eventhash[obj[uid]]; // IE doesn't let you remove DOM object property with - delete try { delete obj[uid]; } catch(e) { obj[uid] = undef; } } }; /** Remove all kind of events from the specified object @method removeAllEvents @static @param {Object} obj DOM element to remove event listeners from. @param {String} [key] unique key to match, when removing events. */ var removeAllEvents = function(obj, key) { if (!obj || !obj[uid]) { return; } Basic.each(eventhash[obj[uid]], function(events, name) { removeEvent(obj, name, key); }); }; return { addEvent: addEvent, removeEvent: removeEvent, removeAllEvents: removeAllEvents }; }); // Included from: src/javascript/runtime/html5/file/FileInput.js /** * FileInput.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/file/FileInput @private */ define("moxie/runtime/html5/file/FileInput", [ "moxie/runtime/html5/Runtime", "moxie/file/File", "moxie/core/utils/Basic", "moxie/core/utils/Dom", "moxie/core/utils/Events", "moxie/core/utils/Mime", "moxie/core/utils/Env" ], function(extensions, File, Basic, Dom, Events, Mime, Env) { function FileInput() { var _options; Basic.extend(this, { init: function(options) { var comp = this, I = comp.getRuntime(), input, shimContainer, mimes, browseButton, zIndex, top; _options = options; // figure out accept string mimes = _options.accept.mimes || Mime.extList2mimes(_options.accept, I.can('filter_by_extension')); shimContainer = I.getShimContainer(); shimContainer.innerHTML = ''; input = Dom.get(I.uid); // prepare file input to be placed underneath the browse_button element Basic.extend(input.style, { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }); browseButton = Dom.get(_options.browse_button); // Route click event to the input[type=file] element for browsers that support such behavior if (I.can('summon_file_dialog')) { if (Dom.getStyle(browseButton, 'position') === 'static') { browseButton.style.position = 'relative'; } zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1; browseButton.style.zIndex = zIndex; shimContainer.style.zIndex = zIndex - 1; Events.addEvent(browseButton, 'click', function(e) { var input = Dom.get(I.uid); if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file] input.click(); } e.preventDefault(); }, comp.uid); } /* Since we have to place input[type=file] on top of the browse_button for some browsers, browse_button loses interactivity, so we restore it here */ top = I.can('summon_file_dialog') ? browseButton : shimContainer; Events.addEvent(top, 'mouseover', function() { comp.trigger('mouseenter'); }, comp.uid); Events.addEvent(top, 'mouseout', function() { comp.trigger('mouseleave'); }, comp.uid); Events.addEvent(top, 'mousedown', function() { comp.trigger('mousedown'); }, comp.uid); Events.addEvent(Dom.get(_options.container), 'mouseup', function() { comp.trigger('mouseup'); }, comp.uid); input.onchange = function onChange(e) { // there should be only one handler for this comp.files = []; Basic.each(this.files, function(file) { var relativePath = ''; if (_options.directory) { // folders are represented by dots, filter them out (Chrome 11+) if (file.name == ".") { // if it looks like a folder... return true; } } if (file.webkitRelativePath) { relativePath = '/' + file.webkitRelativePath.replace(/^\//, ''); } file = new File(I.uid, file); file.relativePath = relativePath; comp.files.push(file); }); // clearing the value enables the user to select the same file again if they want to if (Env.browser !== 'IE' && Env.browser !== 'IEMobile') { this.value = ''; } else { // in IE input[type="file"] is read-only so the only way to reset it is to re-insert it var clone = this.cloneNode(true); this.parentNode.replaceChild(clone, this); clone.onchange = onChange; } if (comp.files.length) { comp.trigger('change'); } }; // ready event is perfectly asynchronous comp.trigger({ type: 'ready', async: true }); shimContainer = null; }, disable: function(state) { var I = this.getRuntime(), input; if ((input = Dom.get(I.uid))) { input.disabled = !!state; } }, destroy: function() { var I = this.getRuntime() , shim = I.getShim() , shimContainer = I.getShimContainer() ; Events.removeAllEvents(shimContainer, this.uid); Events.removeAllEvents(_options && Dom.get(_options.container), this.uid); Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid); if (shimContainer) { shimContainer.innerHTML = ''; } shim.removeInstance(this.uid); _options = shimContainer = shim = null; } }); } return (extensions.FileInput = FileInput); }); // Included from: src/javascript/runtime/html5/file/Blob.js /** * Blob.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/file/Blob @private */ define("moxie/runtime/html5/file/Blob", [ "moxie/runtime/html5/Runtime", "moxie/file/Blob" ], function(extensions, Blob) { function HTML5Blob() { function w3cBlobSlice(blob, start, end) { var blobSlice; if (window.File.prototype.slice) { try { blob.slice(); // depricated version will throw WRONG_ARGUMENTS_ERR exception return blob.slice(start, end); } catch (e) { // depricated slice method return blob.slice(start, end - start); } // slice method got prefixed: https://bugzilla.mozilla.org/show_bug.cgi?id=649672 } else if ((blobSlice = window.File.prototype.webkitSlice || window.File.prototype.mozSlice)) { return blobSlice.call(blob, start, end); } else { return null; // or throw some exception } } this.slice = function() { return new Blob(this.getRuntime().uid, w3cBlobSlice.apply(this, arguments)); }; } return (extensions.Blob = HTML5Blob); }); // Included from: src/javascript/runtime/html5/file/FileDrop.js /** * FileDrop.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/file/FileDrop @private */ define("moxie/runtime/html5/file/FileDrop", [ "moxie/runtime/html5/Runtime", 'moxie/file/File', "moxie/core/utils/Basic", "moxie/core/utils/Dom", "moxie/core/utils/Events", "moxie/core/utils/Mime" ], function(extensions, File, Basic, Dom, Events, Mime) { function FileDrop() { var _files = [], _allowedExts = [], _options, _ruid; Basic.extend(this, { init: function(options) { var comp = this, dropZone; _options = options; _ruid = comp.ruid; // every dropped-in file should have a reference to the runtime _allowedExts = _extractExts(_options.accept); dropZone = _options.container; Events.addEvent(dropZone, 'dragover', function(e) { if (!_hasFiles(e)) { return; } e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; }, comp.uid); Events.addEvent(dropZone, 'drop', function(e) { if (!_hasFiles(e)) { return; } e.preventDefault(); _files = []; // Chrome 21+ accepts folders via Drag'n'Drop if (e.dataTransfer.items && e.dataTransfer.items[0].webkitGetAsEntry) { _readItems(e.dataTransfer.items, function() { comp.files = _files; comp.trigger("drop"); }); } else { Basic.each(e.dataTransfer.files, function(file) { _addFile(file); }); comp.files = _files; comp.trigger("drop"); } }, comp.uid); Events.addEvent(dropZone, 'dragenter', function(e) { comp.trigger("dragenter"); }, comp.uid); Events.addEvent(dropZone, 'dragleave', function(e) { comp.trigger("dragleave"); }, comp.uid); }, destroy: function() { Events.removeAllEvents(_options && Dom.get(_options.container), this.uid); _ruid = _files = _allowedExts = _options = null; } }); function _hasFiles(e) { if (!e.dataTransfer || !e.dataTransfer.types) { // e.dataTransfer.files is not available in Gecko during dragover return false; } var types = Basic.toArray(e.dataTransfer.types || []); return Basic.inArray("Files", types) !== -1 || Basic.inArray("public.file-url", types) !== -1 || // Safari < 5 Basic.inArray("application/x-moz-file", types) !== -1 // Gecko < 1.9.2 (< Firefox 3.6) ; } function _addFile(file, relativePath) { if (_isAcceptable(file)) { var fileObj = new File(_ruid, file); fileObj.relativePath = relativePath || ''; _files.push(fileObj); } } function _extractExts(accept) { var exts = []; for (var i = 0; i < accept.length; i++) { [].push.apply(exts, accept[i].extensions.split(/\s*,\s*/)); } return Basic.inArray('*', exts) === -1 ? exts : []; } function _isAcceptable(file) { if (!_allowedExts.length) { return true; } var ext = Mime.getFileExtension(file.name); return !ext || Basic.inArray(ext, _allowedExts) !== -1; } function _readItems(items, cb) { var entries = []; Basic.each(items, function(item) { var entry = item.webkitGetAsEntry(); // Address #998 (https://code.google.com/p/chromium/issues/detail?id=332579) if (entry) { // file() fails on OSX when the filename contains a special character (e.g. umlaut): see #61 if (entry.isFile) { _addFile(item.getAsFile(), entry.fullPath); } else { entries.push(entry); } } }); if (entries.length) { _readEntries(entries, cb); } else { cb(); } } function _readEntries(entries, cb) { var queue = []; Basic.each(entries, function(entry) { queue.push(function(cbcb) { _readEntry(entry, cbcb); }); }); Basic.inSeries(queue, function() { cb(); }); } function _readEntry(entry, cb) { if (entry.isFile) { entry.file(function(file) { _addFile(file, entry.fullPath); cb(); }, function() { // fire an error event maybe cb(); }); } else if (entry.isDirectory) { _readDirEntry(entry, cb); } else { cb(); // not file, not directory? what then?.. } } function _readDirEntry(dirEntry, cb) { var entries = [], dirReader = dirEntry.createReader(); // keep quering recursively till no more entries function getEntries(cbcb) { dirReader.readEntries(function(moreEntries) { if (moreEntries.length) { [].push.apply(entries, moreEntries); getEntries(cbcb); } else { cbcb(); } }, cbcb); } // ...and you thought FileReader was crazy... getEntries(function() { _readEntries(entries, cb); }); } } return (extensions.FileDrop = FileDrop); }); // Included from: src/javascript/runtime/html5/file/FileReader.js /** * FileReader.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/file/FileReader @private */ define("moxie/runtime/html5/file/FileReader", [ "moxie/runtime/html5/Runtime", "moxie/core/utils/Encode", "moxie/core/utils/Basic" ], function(extensions, Encode, Basic) { function FileReader() { var _fr, _convertToBinary = false; Basic.extend(this, { read: function(op, blob) { var comp = this; comp.result = ''; _fr = new window.FileReader(); _fr.addEventListener('progress', function(e) { comp.trigger(e); }); _fr.addEventListener('load', function(e) { comp.result = _convertToBinary ? _toBinary(_fr.result) : _fr.result; comp.trigger(e); }); _fr.addEventListener('error', function(e) { comp.trigger(e, _fr.error); }); _fr.addEventListener('loadend', function(e) { _fr = null; comp.trigger(e); }); if (Basic.typeOf(_fr[op]) === 'function') { _convertToBinary = false; _fr[op](blob.getSource()); } else if (op === 'readAsBinaryString') { // readAsBinaryString is depricated in general and never existed in IE10+ _convertToBinary = true; _fr.readAsDataURL(blob.getSource()); } }, abort: function() { if (_fr) { _fr.abort(); } }, destroy: function() { _fr = null; } }); function _toBinary(str) { return Encode.atob(str.substring(str.indexOf('base64,') + 7)); } } return (extensions.FileReader = FileReader); }); // Included from: src/javascript/runtime/html5/xhr/XMLHttpRequest.js /** * XMLHttpRequest.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /*global ActiveXObject:true */ /** @class moxie/runtime/html5/xhr/XMLHttpRequest @private */ define("moxie/runtime/html5/xhr/XMLHttpRequest", [ "moxie/runtime/html5/Runtime", "moxie/core/utils/Basic", "moxie/core/utils/Mime", "moxie/core/utils/Url", "moxie/file/File", "moxie/file/Blob", "moxie/xhr/FormData", "moxie/core/Exceptions", "moxie/core/utils/Env" ], function(extensions, Basic, Mime, Url, File, Blob, FormData, x, Env) { function XMLHttpRequest() { var self = this , _xhr , _filename ; Basic.extend(this, { send: function(meta, data) { var target = this , isGecko2_5_6 = (Env.browser === 'Mozilla' && Env.verComp(Env.version, 4, '>=') && Env.verComp(Env.version, 7, '<')) , isAndroidBrowser = Env.browser === 'Android Browser' , mustSendAsBinary = false ; // extract file name _filename = meta.url.replace(/^.+?\/([\w\-\.]+)$/, '$1').toLowerCase(); _xhr = _getNativeXHR(); _xhr.open(meta.method, meta.url, meta.async, meta.user, meta.password); // prepare data to be sent if (data instanceof Blob) { if (data.isDetached()) { mustSendAsBinary = true; } data = data.getSource(); } else if (data instanceof FormData) { if (data.hasBlob()) { if (data.getBlob().isDetached()) { data = _prepareMultipart.call(target, data); // _xhr must be instantiated and be in OPENED state mustSendAsBinary = true; } else if ((isGecko2_5_6 || isAndroidBrowser) && Basic.typeOf(data.getBlob().getSource()) === 'blob' && window.FileReader) { // Gecko 2/5/6 can't send blob in FormData: https://bugzilla.mozilla.org/show_bug.cgi?id=649150 // Android browsers (default one and Dolphin) seem to have the same issue, see: #613 _preloadAndSend.call(target, meta, data); return; // _preloadAndSend will reinvoke send() with transmutated FormData =%D } } // transfer fields to real FormData if (data instanceof FormData) { // if still a FormData, e.g. not mangled by _prepareMultipart() var fd = new window.FormData(); data.each(function(value, name) { if (value instanceof Blob) { fd.append(name, value.getSource()); } else { fd.append(name, value); } }); data = fd; } } // if XHR L2 if (_xhr.upload) { if (meta.withCredentials) { _xhr.withCredentials = true; } _xhr.addEventListener('load', function(e) { target.trigger(e); }); _xhr.addEventListener('error', function(e) { target.trigger(e); }); // additionally listen to progress events _xhr.addEventListener('progress', function(e) { target.trigger(e); }); _xhr.upload.addEventListener('progress', function(e) { target.trigger({ type: 'UploadProgress', loaded: e.loaded, total: e.total }); }); // ... otherwise simulate XHR L2 } else { _xhr.onreadystatechange = function onReadyStateChange() { // fake Level 2 events switch (_xhr.readyState) { case 1: // XMLHttpRequest.OPENED // readystatechanged is fired twice for OPENED state (in IE and Mozilla) - neu break; // looks like HEADERS_RECEIVED (state 2) is not reported in Opera (or it's old versions) - neu case 2: // XMLHttpRequest.HEADERS_RECEIVED break; case 3: // XMLHttpRequest.LOADING // try to fire progress event for not XHR L2 var total, loaded; try { if (Url.hasSameOrigin(meta.url)) { // Content-Length not accessible for cross-domain on some browsers total = _xhr.getResponseHeader('Content-Length') || 0; // old Safari throws an exception here } if (_xhr.responseText) { // responseText was introduced in IE7 loaded = _xhr.responseText.length; } } catch(ex) { total = loaded = 0; } target.trigger({ type: 'progress', lengthComputable: !!total, total: parseInt(total, 10), loaded: loaded }); break; case 4: // XMLHttpRequest.DONE // release readystatechange handler (mostly for IE) _xhr.onreadystatechange = function() {}; // usually status 0 is returned when server is unreachable, but FF also fails to status 0 for 408 timeout if (_xhr.status === 0) { target.trigger('error'); } else { target.trigger('load'); } break; } }; } // set request headers if (!Basic.isEmptyObj(meta.headers)) { Basic.each(meta.headers, function(value, header) { _xhr.setRequestHeader(header, value); }); } // request response type if ("" !== meta.responseType && 'responseType' in _xhr) { if ('json' === meta.responseType && !Env.can('return_response_type', 'json')) { // we can fake this one _xhr.responseType = 'text'; } else { _xhr.responseType = meta.responseType; } } // send ... if (!mustSendAsBinary) { _xhr.send(data); } else { if (_xhr.sendAsBinary) { // Gecko _xhr.sendAsBinary(data); } else { // other browsers having support for typed arrays (function() { // mimic Gecko's sendAsBinary var ui8a = new Uint8Array(data.length); for (var i = 0; i < data.length; i++) { ui8a[i] = (data.charCodeAt(i) & 0xff); } _xhr.send(ui8a.buffer); }()); } } target.trigger('loadstart'); }, getStatus: function() { // according to W3C spec it should return 0 for readyState < 3, but instead it throws an exception try { if (_xhr) { return _xhr.status; } } catch(ex) {} return 0; }, getResponse: function(responseType) { var I = this.getRuntime(); try { switch (responseType) { case 'blob': var file = new File(I.uid, _xhr.response); // try to extract file name from content-disposition if possible (might be - not, if CORS for example) var disposition = _xhr.getResponseHeader('Content-Disposition'); if (disposition) { // extract filename from response header if available var match = disposition.match(/filename=([\'\"'])([^\1]+)\1/); if (match) { _filename = match[2]; } } file.name = _filename; // pre-webkit Opera doesn't set type property on the blob response if (!file.type) { file.type = Mime.getFileMime(_filename); } return file; case 'json': if (!Env.can('return_response_type', 'json')) { return _xhr.status === 200 && !!window.JSON ? JSON.parse(_xhr.responseText) : null; } return _xhr.response; case 'document': return _getDocument(_xhr); default: return _xhr.responseText !== '' ? _xhr.responseText : null; // against the specs, but for consistency across the runtimes } } catch(ex) { return null; } }, getAllResponseHeaders: function() { try { return _xhr.getAllResponseHeaders(); } catch(ex) {} return ''; }, abort: function() { if (_xhr) { _xhr.abort(); } }, destroy: function() { self = _filename = null; } }); // here we go... ugly fix for ugly bug function _preloadAndSend(meta, data) { var target = this, blob, fr; // get original blob blob = data.getBlob().getSource(); // preload blob in memory to be sent as binary string fr = new window.FileReader(); fr.onload = function() { // overwrite original blob data.append(data.getBlobName(), new Blob(null, { type: blob.type, data: fr.result })); // invoke send operation again self.send.call(target, meta, data); }; fr.readAsBinaryString(blob); } function _getNativeXHR() { if (window.XMLHttpRequest && !(Env.browser === 'IE' && Env.verComp(Env.version, 8, '<'))) { // IE7 has native XHR but it's buggy return new window.XMLHttpRequest(); } else { return (function() { var progIDs = ['Msxml2.XMLHTTP.6.0', 'Microsoft.XMLHTTP']; // if 6.0 available, use it, otherwise failback to default 3.0 for (var i = 0; i < progIDs.length; i++) { try { return new ActiveXObject(progIDs[i]); } catch (ex) {} } })(); } } // @credits Sergey Ilinsky (http://www.ilinsky.com/) function _getDocument(xhr) { var rXML = xhr.responseXML; var rText = xhr.responseText; // Try parsing responseText (@see: http://www.ilinsky.com/articles/XMLHttpRequest/#bugs-ie-responseXML-content-type) if (Env.browser === 'IE' && rText && rXML && !rXML.documentElement && /[^\/]+\/[^\+]+\+xml/.test(xhr.getResponseHeader("Content-Type"))) { rXML = new window.ActiveXObject("Microsoft.XMLDOM"); rXML.async = false; rXML.validateOnParse = false; rXML.loadXML(rText); } // Check if there is no error in document if (rXML) { if ((Env.browser === 'IE' && rXML.parseError !== 0) || !rXML.documentElement || rXML.documentElement.tagName === "parsererror") { return null; } } return rXML; } function _prepareMultipart(fd) { var boundary = '----moxieboundary' + new Date().getTime() , dashdash = '--' , crlf = '\r\n' , multipart = '' , I = this.getRuntime() ; if (!I.can('send_binary_string')) { throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR); } _xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); // append multipart parameters fd.each(function(value, name) { // Firefox 3.6 failed to convert multibyte characters to UTF-8 in sendAsBinary(), // so we try it here ourselves with: unescape(encodeURIComponent(value)) if (value instanceof Blob) { // Build RFC2388 blob multipart += dashdash + boundary + crlf + 'Content-Disposition: form-data; name="' + name + '"; filename="' + unescape(encodeURIComponent(value.name || 'blob')) + '"' + crlf + 'Content-Type: ' + (value.type || 'application/octet-stream') + crlf + crlf + value.getSource() + crlf; } else { multipart += dashdash + boundary + crlf + 'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf + unescape(encodeURIComponent(value)) + crlf; } }); multipart += dashdash + boundary + dashdash + crlf; return multipart; } } return (extensions.XMLHttpRequest = XMLHttpRequest); }); // Included from: src/javascript/runtime/html5/utils/BinaryReader.js /** * BinaryReader.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/utils/BinaryReader @private */ define("moxie/runtime/html5/utils/BinaryReader", [ "moxie/core/utils/Basic" ], function(Basic) { function BinaryReader(data) { if (data instanceof ArrayBuffer) { ArrayBufferReader.apply(this, arguments); } else { UTF16StringReader.apply(this, arguments); } }   Basic.extend(BinaryReader.prototype, { littleEndian: false, read: function(idx, size) { var sum, mv, i; if (idx + size > this.length()) { throw new Error("You are trying to read outside the source boundaries."); } mv = this.littleEndian ? 0 : -8 * (size - 1) ; for (i = 0, sum = 0; i < size; i++) { sum |= (this.readByteAt(idx + i) << Math.abs(mv + i*8)); } return sum; }, write: function(idx, num, size) { var mv, i, str = ''; if (idx > this.length()) { throw new Error("You are trying to write outside the source boundaries."); } mv = this.littleEndian ? 0 : -8 * (size - 1) ; for (i = 0; i < size; i++) { this.writeByteAt(idx + i, (num >> Math.abs(mv + i*8)) & 255); } }, BYTE: function(idx) { return this.read(idx, 1); }, SHORT: function(idx) { return this.read(idx, 2); }, LONG: function(idx) { return this.read(idx, 4); }, SLONG: function(idx) { // 2's complement notation var num = this.read(idx, 4); return (num > 2147483647 ? num - 4294967296 : num); }, CHAR: function(idx) { return String.fromCharCode(this.read(idx, 1)); }, STRING: function(idx, count) { return this.asArray('CHAR', idx, count).join(''); }, asArray: function(type, idx, count) { var values = []; for (var i = 0; i < count; i++) { values[i] = this[type](idx + i); } return values; } }); function ArrayBufferReader(data) { var _dv = new DataView(data); Basic.extend(this, { readByteAt: function(idx) { return _dv.getUint8(idx); }, writeByteAt: function(idx, value) { _dv.setUint8(idx, value); }, SEGMENT: function(idx, size, value) { switch (arguments.length) { case 2: return data.slice(idx, idx + size); case 1: return data.slice(idx); case 3: if (value === null) { value = new ArrayBuffer(); } if (value instanceof ArrayBuffer) { var arr = new Uint8Array(this.length() - size + value.byteLength); if (idx > 0) { arr.set(new Uint8Array(data.slice(0, idx)), 0); } arr.set(new Uint8Array(value), idx); arr.set(new Uint8Array(data.slice(idx + size)), idx + value.byteLength); this.clear(); data = arr.buffer; _dv = new DataView(data); break; } default: return data; } }, length: function() { return data ? data.byteLength : 0; }, clear: function() { _dv = data = null; } }); } function UTF16StringReader(data) { Basic.extend(this, { readByteAt: function(idx) { return data.charCodeAt(idx); }, writeByteAt: function(idx, value) { putstr(String.fromCharCode(value), idx, 1); }, SEGMENT: function(idx, length, segment) { switch (arguments.length) { case 1: return data.substr(idx); case 2: return data.substr(idx, length); case 3: putstr(segment !== null ? segment : '', idx, length); break; default: return data; } }, length: function() { return data ? data.length : 0; }, clear: function() { data = null; } }); function putstr(segment, idx, length) { length = arguments.length === 3 ? length : data.length - idx - 1; data = data.substr(0, idx) + segment + data.substr(length + idx); } } return BinaryReader; }); // Included from: src/javascript/runtime/html5/image/JPEGHeaders.js /** * JPEGHeaders.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/image/JPEGHeaders @private */ define("moxie/runtime/html5/image/JPEGHeaders", [ "moxie/runtime/html5/utils/BinaryReader", "moxie/core/Exceptions" ], function(BinaryReader, x) { return function JPEGHeaders(data) { var headers = [], _br, idx, marker, length = 0; _br = new BinaryReader(data); // Check if data is jpeg if (_br.SHORT(0) !== 0xFFD8) { _br.clear(); throw new x.ImageError(x.ImageError.WRONG_FORMAT); } idx = 2; while (idx <= _br.length()) { marker = _br.SHORT(idx); // omit RST (restart) markers if (marker >= 0xFFD0 && marker <= 0xFFD7) { idx += 2; continue; } // no headers allowed after SOS marker if (marker === 0xFFDA || marker === 0xFFD9) { break; } length = _br.SHORT(idx + 2) + 2; // APPn marker detected if (marker >= 0xFFE1 && marker <= 0xFFEF) { headers.push({ hex: marker, name: 'APP' + (marker & 0x000F), start: idx, length: length, segment: _br.SEGMENT(idx, length) }); } idx += length; } _br.clear(); return { headers: headers, restore: function(data) { var max, i, br; br = new BinaryReader(data); idx = br.SHORT(2) == 0xFFE0 ? 4 + br.SHORT(4) : 2; for (i = 0, max = headers.length; i < max; i++) { br.SEGMENT(idx, 0, headers[i].segment); idx += headers[i].length; } data = br.SEGMENT(); br.clear(); return data; }, strip: function(data) { var br, headers, jpegHeaders, i; jpegHeaders = new JPEGHeaders(data); headers = jpegHeaders.headers; jpegHeaders.purge(); br = new BinaryReader(data); i = headers.length; while (i--) { br.SEGMENT(headers[i].start, headers[i].length, ''); } data = br.SEGMENT(); br.clear(); return data; }, get: function(name) { var array = []; for (var i = 0, max = headers.length; i < max; i++) { if (headers[i].name === name.toUpperCase()) { array.push(headers[i].segment); } } return array; }, set: function(name, segment) { var array = [], i, ii, max; if (typeof(segment) === 'string') { array.push(segment); } else { array = segment; } for (i = ii = 0, max = headers.length; i < max; i++) { if (headers[i].name === name.toUpperCase()) { headers[i].segment = array[ii]; headers[i].length = array[ii].length; ii++; } if (ii >= array.length) { break; } } }, purge: function() { this.headers = headers = []; } }; }; }); // Included from: src/javascript/runtime/html5/image/ExifParser.js /** * ExifParser.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/image/ExifParser @private */ define("moxie/runtime/html5/image/ExifParser", [ "moxie/core/utils/Basic", "moxie/runtime/html5/utils/BinaryReader", "moxie/core/Exceptions" ], function(Basic, BinaryReader, x) { function ExifParser(data) { var __super__, tags, tagDescs, offsets, idx, Tiff; BinaryReader.call(this, data); tags = { tiff: { /* The image orientation viewed in terms of rows and columns. 1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side. 2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side. 3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side. 4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side. 5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top. 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top. 7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom. 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom. */ 0x0112: 'Orientation', 0x010E: 'ImageDescription', 0x010F: 'Make', 0x0110: 'Model', 0x0131: 'Software', 0x8769: 'ExifIFDPointer', 0x8825: 'GPSInfoIFDPointer' }, exif: { 0x9000: 'ExifVersion', 0xA001: 'ColorSpace', 0xA002: 'PixelXDimension', 0xA003: 'PixelYDimension', 0x9003: 'DateTimeOriginal', 0x829A: 'ExposureTime', 0x829D: 'FNumber', 0x8827: 'ISOSpeedRatings', 0x9201: 'ShutterSpeedValue', 0x9202: 'ApertureValue' , 0x9207: 'MeteringMode', 0x9208: 'LightSource', 0x9209: 'Flash', 0x920A: 'FocalLength', 0xA402: 'ExposureMode', 0xA403: 'WhiteBalance', 0xA406: 'SceneCaptureType', 0xA404: 'DigitalZoomRatio', 0xA408: 'Contrast', 0xA409: 'Saturation', 0xA40A: 'Sharpness' }, gps: { 0x0000: 'GPSVersionID', 0x0001: 'GPSLatitudeRef', 0x0002: 'GPSLatitude', 0x0003: 'GPSLongitudeRef', 0x0004: 'GPSLongitude' }, thumb: { 0x0201: 'JPEGInterchangeFormat', 0x0202: 'JPEGInterchangeFormatLength' } }; tagDescs = { 'ColorSpace': { 1: 'sRGB', 0: 'Uncalibrated' }, 'MeteringMode': { 0: 'Unknown', 1: 'Average', 2: 'CenterWeightedAverage', 3: 'Spot', 4: 'MultiSpot', 5: 'Pattern', 6: 'Partial', 255: 'Other' }, 'LightSource': { 1: 'Daylight', 2: 'Fliorescent', 3: 'Tungsten', 4: 'Flash', 9: 'Fine weather', 10: 'Cloudy weather', 11: 'Shade', 12: 'Daylight fluorescent (D 5700 - 7100K)', 13: 'Day white fluorescent (N 4600 -5400K)', 14: 'Cool white fluorescent (W 3900 - 4500K)', 15: 'White fluorescent (WW 3200 - 3700K)', 17: 'Standard light A', 18: 'Standard light B', 19: 'Standard light C', 20: 'D55', 21: 'D65', 22: 'D75', 23: 'D50', 24: 'ISO studio tungsten', 255: 'Other' }, 'Flash': { 0x0000: 'Flash did not fire', 0x0001: 'Flash fired', 0x0005: 'Strobe return light not detected', 0x0007: 'Strobe return light detected', 0x0009: 'Flash fired, compulsory flash mode', 0x000D: 'Flash fired, compulsory flash mode, return light not detected', 0x000F: 'Flash fired, compulsory flash mode, return light detected', 0x0010: 'Flash did not fire, compulsory flash mode', 0x0018: 'Flash did not fire, auto mode', 0x0019: 'Flash fired, auto mode', 0x001D: 'Flash fired, auto mode, return light not detected', 0x001F: 'Flash fired, auto mode, return light detected', 0x0020: 'No flash function', 0x0041: 'Flash fired, red-eye reduction mode', 0x0045: 'Flash fired, red-eye reduction mode, return light not detected', 0x0047: 'Flash fired, red-eye reduction mode, return light detected', 0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode', 0x004D: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected', 0x004F: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected', 0x0059: 'Flash fired, auto mode, red-eye reduction mode', 0x005D: 'Flash fired, auto mode, return light not detected, red-eye reduction mode', 0x005F: 'Flash fired, auto mode, return light detected, red-eye reduction mode' }, 'ExposureMode': { 0: 'Auto exposure', 1: 'Manual exposure', 2: 'Auto bracket' }, 'WhiteBalance': { 0: 'Auto white balance', 1: 'Manual white balance' }, 'SceneCaptureType': { 0: 'Standard', 1: 'Landscape', 2: 'Portrait', 3: 'Night scene' }, 'Contrast': { 0: 'Normal', 1: 'Soft', 2: 'Hard' }, 'Saturation': { 0: 'Normal', 1: 'Low saturation', 2: 'High saturation' }, 'Sharpness': { 0: 'Normal', 1: 'Soft', 2: 'Hard' }, // GPS related 'GPSLatitudeRef': { N: 'North latitude', S: 'South latitude' }, 'GPSLongitudeRef': { E: 'East longitude', W: 'West longitude' } }; offsets = { tiffHeader: 10 }; idx = offsets.tiffHeader; __super__ = { clear: this.clear }; // Public functions Basic.extend(this, { read: function() { try { return ExifParser.prototype.read.apply(this, arguments); } catch (ex) { throw new x.ImageError(x.ImageError.INVALID_META_ERR); } }, write: function() { try { return ExifParser.prototype.write.apply(this, arguments); } catch (ex) { throw new x.ImageError(x.ImageError.INVALID_META_ERR); } }, UNDEFINED: function() { return this.BYTE.apply(this, arguments); }, RATIONAL: function(idx) { return this.LONG(idx) / this.LONG(idx + 4) }, SRATIONAL: function(idx) { return this.SLONG(idx) / this.SLONG(idx + 4) }, ASCII: function(idx) { return this.CHAR(idx); }, TIFF: function() { return Tiff || null; }, EXIF: function() { var Exif = null; if (offsets.exifIFD) { try { Exif = extractTags.call(this, offsets.exifIFD, tags.exif); } catch(ex) { return null; } // Fix formatting of some tags if (Exif.ExifVersion && Basic.typeOf(Exif.ExifVersion) === 'array') { for (var i = 0, exifVersion = ''; i < Exif.ExifVersion.length; i++) { exifVersion += String.fromCharCode(Exif.ExifVersion[i]); } Exif.ExifVersion = exifVersion; } } return Exif; }, GPS: function() { var GPS = null; if (offsets.gpsIFD) { try { GPS = extractTags.call(this, offsets.gpsIFD, tags.gps); } catch (ex) { return null; } // iOS devices (and probably some others) do not put in GPSVersionID tag (why?..) if (GPS.GPSVersionID && Basic.typeOf(GPS.GPSVersionID) === 'array') { GPS.GPSVersionID = GPS.GPSVersionID.join('.'); } } return GPS; }, thumb: function() { if (offsets.IFD1) { try { var IFD1Tags = extractTags.call(this, offsets.IFD1, tags.thumb); if ('JPEGInterchangeFormat' in IFD1Tags) { return this.SEGMENT(offsets.tiffHeader + IFD1Tags.JPEGInterchangeFormat, IFD1Tags.JPEGInterchangeFormatLength); } } catch (ex) {} } return null; }, setExif: function(tag, value) { // Right now only setting of width/height is possible if (tag !== 'PixelXDimension' && tag !== 'PixelYDimension') { return false; } return setTag.call(this, 'exif', tag, value); }, clear: function() { __super__.clear(); data = tags = tagDescs = Tiff = offsets = __super__ = null; } }); // Check if that's APP1 and that it has EXIF if (this.SHORT(0) !== 0xFFE1 || this.STRING(4, 5).toUpperCase() !== "EXIF\0") { throw new x.ImageError(x.ImageError.INVALID_META_ERR); } // Set read order of multi-byte data this.littleEndian = (this.SHORT(idx) == 0x4949); // Check if always present bytes are indeed present if (this.SHORT(idx+=2) !== 0x002A) { throw new x.ImageError(x.ImageError.INVALID_META_ERR); } offsets.IFD0 = offsets.tiffHeader + this.LONG(idx += 2); Tiff = extractTags.call(this, offsets.IFD0, tags.tiff); if ('ExifIFDPointer' in Tiff) { offsets.exifIFD = offsets.tiffHeader + Tiff.ExifIFDPointer; delete Tiff.ExifIFDPointer; } if ('GPSInfoIFDPointer' in Tiff) { offsets.gpsIFD = offsets.tiffHeader + Tiff.GPSInfoIFDPointer; delete Tiff.GPSInfoIFDPointer; } if (Basic.isEmptyObj(Tiff)) { Tiff = null; } // check if we have a thumb as well var IFD1Offset = this.LONG(offsets.IFD0 + this.SHORT(offsets.IFD0) * 12 + 2); if (IFD1Offset) { offsets.IFD1 = offsets.tiffHeader + IFD1Offset; } function extractTags(IFD_offset, tags2extract) { var data = this; var length, i, tag, type, count, size, offset, value, values = [], hash = {}; var types = { 1 : 'BYTE', 7 : 'UNDEFINED', 2 : 'ASCII', 3 : 'SHORT', 4 : 'LONG', 5 : 'RATIONAL', 9 : 'SLONG', 10: 'SRATIONAL' }; var sizes = { 'BYTE' : 1, 'UNDEFINED' : 1, 'ASCII' : 1, 'SHORT' : 2, 'LONG' : 4, 'RATIONAL' : 8, 'SLONG' : 4, 'SRATIONAL' : 8 }; length = data.SHORT(IFD_offset); // The size of APP1 including all these elements shall not exceed the 64 Kbytes specified in the JPEG standard. for (i = 0; i < length; i++) { values = []; // Set binary reader pointer to beginning of the next tag offset = IFD_offset + 2 + i*12; tag = tags2extract[data.SHORT(offset)]; if (tag === undefined) { continue; // Not the tag we requested } type = types[data.SHORT(offset+=2)]; count = data.LONG(offset+=2); size = sizes[type]; if (!size) { throw new x.ImageError(x.ImageError.INVALID_META_ERR); } offset += 4; // tag can only fit 4 bytes of data, if data is larger we should look outside if (size * count > 4) { // instead of data tag contains an offset of the data offset = data.LONG(offset) + offsets.tiffHeader; } // in case we left the boundaries of data throw an early exception if (offset + size * count >= this.length()) { throw new x.ImageError(x.ImageError.INVALID_META_ERR); } // special care for the string if (type === 'ASCII') { hash[tag] = Basic.trim(data.STRING(offset, count).replace(/\0$/, '')); // strip trailing NULL continue; } else { values = data.asArray(type, offset, count); value = (count == 1 ? values[0] : values); if (tagDescs.hasOwnProperty(tag) && typeof value != 'object') { hash[tag] = tagDescs[tag][value]; } else { hash[tag] = value; } } } return hash; } // At the moment only setting of simple (LONG) values, that do not require offset recalculation, is supported function setTag(ifd, tag, value) { var offset, length, tagOffset, valueOffset = 0; // If tag name passed translate into hex key if (typeof(tag) === 'string') { var tmpTags = tags[ifd.toLowerCase()]; for (var hex in tmpTags) { if (tmpTags[hex] === tag) { tag = hex; break; } } } offset = offsets[ifd.toLowerCase() + 'IFD']; length = this.SHORT(offset); for (var i = 0; i < length; i++) { tagOffset = offset + 12 * i + 2; if (this.SHORT(tagOffset) == tag) { valueOffset = tagOffset + 8; break; } } if (!valueOffset) { return false; } try { this.write(valueOffset, value, 4); } catch(ex) { return false; } return true; } } ExifParser.prototype = BinaryReader.prototype; return ExifParser; }); // Included from: src/javascript/runtime/html5/image/JPEG.js /** * JPEG.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/image/JPEG @private */ define("moxie/runtime/html5/image/JPEG", [ "moxie/core/utils/Basic", "moxie/core/Exceptions", "moxie/runtime/html5/image/JPEGHeaders", "moxie/runtime/html5/utils/BinaryReader", "moxie/runtime/html5/image/ExifParser" ], function(Basic, x, JPEGHeaders, BinaryReader, ExifParser) { function JPEG(data) { var _br, _hm, _ep, _info; _br = new BinaryReader(data); // check if it is jpeg if (_br.SHORT(0) !== 0xFFD8) { throw new x.ImageError(x.ImageError.WRONG_FORMAT); } // backup headers _hm = new JPEGHeaders(data); // extract exif info try { _ep = new ExifParser(_hm.get('app1')[0]); } catch(ex) {} // get dimensions _info = _getDimensions.call(this); Basic.extend(this, { type: 'image/jpeg', size: _br.length(), width: _info && _info.width || 0, height: _info && _info.height || 0, setExif: function(tag, value) { if (!_ep) { return false; // or throw an exception } if (Basic.typeOf(tag) === 'object') { Basic.each(tag, function(value, tag) { _ep.setExif(tag, value); }); } else { _ep.setExif(tag, value); } // update internal headers _hm.set('app1', _ep.SEGMENT()); }, writeHeaders: function() { if (!arguments.length) { // if no arguments passed, update headers internally return _hm.restore(data); } return _hm.restore(arguments[0]); }, stripHeaders: function(data) { return _hm.strip(data); }, purge: function() { _purge.call(this); } }); if (_ep) { this.meta = { tiff: _ep.TIFF(), exif: _ep.EXIF(), gps: _ep.GPS(), thumb: _getThumb() }; } function _getDimensions(br) { var idx = 0 , marker , length ; if (!br) { br = _br; } // examine all through the end, since some images might have very large APP segments while (idx <= br.length()) { marker = br.SHORT(idx += 2); if (marker >= 0xFFC0 && marker <= 0xFFC3) { // SOFn idx += 5; // marker (2 bytes) + length (2 bytes) + Sample precision (1 byte) return { height: br.SHORT(idx), width: br.SHORT(idx += 2) }; } length = br.SHORT(idx += 2); idx += length - 2; } return null; } function _getThumb() { var data = _ep.thumb() , br , info ; if (data) { br = new BinaryReader(data); info = _getDimensions(br); br.clear(); if (info) { info.data = data; return info; } } return null; } function _purge() { if (!_ep || !_hm || !_br) { return; // ignore any repeating purge requests } _ep.clear(); _hm.purge(); _br.clear(); _info = _hm = _ep = _br = null; } } return JPEG; }); // Included from: src/javascript/runtime/html5/image/PNG.js /** * PNG.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/image/PNG @private */ define("moxie/runtime/html5/image/PNG", [ "moxie/core/Exceptions", "moxie/core/utils/Basic", "moxie/runtime/html5/utils/BinaryReader" ], function(x, Basic, BinaryReader) { function PNG(data) { var _br, _hm, _ep, _info; _br = new BinaryReader(data); // check if it's png (function() { var idx = 0, i = 0 , signature = [0x8950, 0x4E47, 0x0D0A, 0x1A0A] ; for (i = 0; i < signature.length; i++, idx += 2) { if (signature[i] != _br.SHORT(idx)) { throw new x.ImageError(x.ImageError.WRONG_FORMAT); } } }()); function _getDimensions() { var chunk, idx; chunk = _getChunkAt.call(this, 8); if (chunk.type == 'IHDR') { idx = chunk.start; return { width: _br.LONG(idx), height: _br.LONG(idx += 4) }; } return null; } function _purge() { if (!_br) { return; // ignore any repeating purge requests } _br.clear(); data = _info = _hm = _ep = _br = null; } _info = _getDimensions.call(this); Basic.extend(this, { type: 'image/png', size: _br.length(), width: _info.width, height: _info.height, purge: function() { _purge.call(this); } }); // for PNG we can safely trigger purge automatically, as we do not keep any data for later _purge.call(this); function _getChunkAt(idx) { var length, type, start, CRC; length = _br.LONG(idx); type = _br.STRING(idx += 4, 4); start = idx += 4; CRC = _br.LONG(idx + length); return { length: length, type: type, start: start, CRC: CRC }; } } return PNG; }); // Included from: src/javascript/runtime/html5/image/ImageInfo.js /** * ImageInfo.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/image/ImageInfo @private */ define("moxie/runtime/html5/image/ImageInfo", [ "moxie/core/utils/Basic", "moxie/core/Exceptions", "moxie/runtime/html5/image/JPEG", "moxie/runtime/html5/image/PNG" ], function(Basic, x, JPEG, PNG) { /** Optional image investigation tool for HTML5 runtime. Provides the following features: - ability to distinguish image type (JPEG or PNG) by signature - ability to extract image width/height directly from it's internals, without preloading in memory (fast) - ability to extract APP headers from JPEGs (Exif, GPS, etc) - ability to replace width/height tags in extracted JPEG headers - ability to restore APP headers, that were for example stripped during image manipulation @class ImageInfo @constructor @param {String} data Image source as binary string */ return function(data) { var _cs = [JPEG, PNG], _img; // figure out the format, throw: ImageError.WRONG_FORMAT if not supported _img = (function() { for (var i = 0; i < _cs.length; i++) { try { return new _cs[i](data); } catch (ex) { // console.info(ex); } } throw new x.ImageError(x.ImageError.WRONG_FORMAT); }()); Basic.extend(this, { /** Image Mime Type extracted from it's depths @property type @type {String} @default '' */ type: '', /** Image size in bytes @property size @type {Number} @default 0 */ size: 0, /** Image width extracted from image source @property width @type {Number} @default 0 */ width: 0, /** Image height extracted from image source @property height @type {Number} @default 0 */ height: 0, /** Sets Exif tag. Currently applicable only for width and height tags. Obviously works only with JPEGs. @method setExif @param {String} tag Tag to set @param {Mixed} value Value to assign to the tag */ setExif: function() {}, /** Restores headers to the source. @method writeHeaders @param {String} data Image source as binary string @return {String} Updated binary string */ writeHeaders: function(data) { return data; }, /** Strip all headers from the source. @method stripHeaders @param {String} data Image source as binary string @return {String} Updated binary string */ stripHeaders: function(data) { return data; }, /** Dispose resources. @method purge */ purge: function() { data = null; } }); Basic.extend(this, _img); this.purge = function() { _img.purge(); _img = null; }; }; }); // Included from: src/javascript/runtime/html5/image/MegaPixel.js /** (The MIT License) Copyright (c) 2012 Shinichi Tomita ; Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Mega pixel image rendering library for iOS6 Safari * * Fixes iOS6 Safari's image file rendering issue for large size image (over mega-pixel), * which causes unexpected subsampling when drawing it in canvas. * By using this library, you can safely render the image with proper stretching. * * Copyright (c) 2012 Shinichi Tomita * Released under the MIT license */ /** @class moxie/runtime/html5/image/MegaPixel @private */ define("moxie/runtime/html5/image/MegaPixel", [], function() { /** * Rendering image element (with resizing) into the canvas element */ function renderImageToCanvas(img, canvas, options) { var iw = img.naturalWidth, ih = img.naturalHeight; var width = options.width, height = options.height; var x = options.x || 0, y = options.y || 0; var ctx = canvas.getContext('2d'); if (detectSubsampling(img)) { iw /= 2; ih /= 2; } var d = 1024; // size of tiling canvas var tmpCanvas = document.createElement('canvas'); tmpCanvas.width = tmpCanvas.height = d; var tmpCtx = tmpCanvas.getContext('2d'); var vertSquashRatio = detectVerticalSquash(img, iw, ih); var sy = 0; while (sy < ih) { var sh = sy + d > ih ? ih - sy : d; var sx = 0; while (sx < iw) { var sw = sx + d > iw ? iw - sx : d; tmpCtx.clearRect(0, 0, d, d); tmpCtx.drawImage(img, -sx, -sy); var dx = (sx * width / iw + x) << 0; var dw = Math.ceil(sw * width / iw); var dy = (sy * height / ih / vertSquashRatio + y) << 0; var dh = Math.ceil(sh * height / ih / vertSquashRatio); ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh); sx += d; } sy += d; } tmpCanvas = tmpCtx = null; } /** * Detect subsampling in loaded image. * In iOS, larger images than 2M pixels may be subsampled in rendering. */ function detectSubsampling(img) { var iw = img.naturalWidth, ih = img.naturalHeight; if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image var canvas = document.createElement('canvas'); canvas.width = canvas.height = 1; var ctx = canvas.getContext('2d'); ctx.drawImage(img, -iw + 1, 0); // subsampled image becomes half smaller in rendering size. // check alpha channel value to confirm image is covering edge pixel or not. // if alpha value is 0 image is not covering, hence subsampled. return ctx.getImageData(0, 0, 1, 1).data[3] === 0; } else { return false; } } /** * Detecting vertical squash in loaded image. * Fixes a bug which squash image vertically while drawing into canvas for some images. */ function detectVerticalSquash(img, iw, ih) { var canvas = document.createElement('canvas'); canvas.width = 1; canvas.height = ih; var ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); var data = ctx.getImageData(0, 0, 1, ih).data; // search image edge pixel position in case it is squashed vertically. var sy = 0; var ey = ih; var py = ih; while (py > sy) { var alpha = data[(py - 1) * 4 + 3]; if (alpha === 0) { ey = py; } else { sy = py; } py = (ey + sy) >> 1; } canvas = null; var ratio = (py / ih); return (ratio === 0) ? 1 : ratio; } return { isSubsampled: detectSubsampling, renderTo: renderImageToCanvas }; }); // Included from: src/javascript/runtime/html5/image/Image.js /** * Image.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html5/image/Image @private */ define("moxie/runtime/html5/image/Image", [ "moxie/runtime/html5/Runtime", "moxie/core/utils/Basic", "moxie/core/Exceptions", "moxie/core/utils/Encode", "moxie/file/Blob", "moxie/file/File", "moxie/runtime/html5/image/ImageInfo", "moxie/runtime/html5/image/MegaPixel", "moxie/core/utils/Mime", "moxie/core/utils/Env" ], function(extensions, Basic, x, Encode, Blob, File, ImageInfo, MegaPixel, Mime, Env) { function HTML5Image() { var me = this , _img, _imgInfo, _canvas, _binStr, _blob , _modified = false // is set true whenever image is modified , _preserveHeaders = true ; Basic.extend(this, { loadFromBlob: function(blob) { var comp = this, I = comp.getRuntime() , asBinary = arguments.length > 1 ? arguments[1] : true ; if (!I.can('access_binary')) { throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR); } _blob = blob; if (blob.isDetached()) { _binStr = blob.getSource(); _preload.call(this, _binStr); return; } else { _readAsDataUrl.call(this, blob.getSource(), function(dataUrl) { if (asBinary) { _binStr = _toBinary(dataUrl); } _preload.call(comp, dataUrl); }); } }, loadFromImage: function(img, exact) { this.meta = img.meta; _blob = new File(null, { name: img.name, size: img.size, type: img.type }); _preload.call(this, exact ? (_binStr = img.getAsBinaryString()) : img.getAsDataURL()); }, getInfo: function() { var I = this.getRuntime(), info; if (!_imgInfo && _binStr && I.can('access_image_binary')) { _imgInfo = new ImageInfo(_binStr); } info = { width: _getImg().width || 0, height: _getImg().height || 0, type: _blob.type || Mime.getFileMime(_blob.name), size: _binStr && _binStr.length || _blob.size || 0, name: _blob.name || '', meta: _imgInfo && _imgInfo.meta || this.meta || {} }; // store thumbnail data as blob if (info.meta && info.meta.thumb && !(info.meta.thumb.data instanceof Blob)) { info.meta.thumb.data = new Blob(null, { type: 'image/jpeg', data: info.meta.thumb.data }); } return info; }, downsize: function() { _downsize.apply(this, arguments); }, getAsCanvas: function() { if (_canvas) { _canvas.id = this.uid + '_canvas'; } return _canvas; }, getAsBlob: function(type, quality) { if (type !== this.type) { // if different mime type requested prepare image for conversion _downsize.call(this, this.width, this.height, false); } return new File(null, { name: _blob.name || '', type: type, data: me.getAsBinaryString.call(this, type, quality) }); }, getAsDataURL: function(type) { var quality = arguments[1] || 90; // if image has not been modified, return the source right away if (!_modified) { return _img.src; } if ('image/jpeg' !== type) { return _canvas.toDataURL('image/png'); } else { try { // older Geckos used to result in an exception on quality argument return _canvas.toDataURL('image/jpeg', quality/100); } catch (ex) { return _canvas.toDataURL('image/jpeg'); } } }, getAsBinaryString: function(type, quality) { // if image has not been modified, return the source right away if (!_modified) { // if image was not loaded from binary string if (!_binStr) { _binStr = _toBinary(me.getAsDataURL(type, quality)); } return _binStr; } if ('image/jpeg' !== type) { _binStr = _toBinary(me.getAsDataURL(type, quality)); } else { var dataUrl; // if jpeg if (!quality) { quality = 90; } try { // older Geckos used to result in an exception on quality argument dataUrl = _canvas.toDataURL('image/jpeg', quality/100); } catch (ex) { dataUrl = _canvas.toDataURL('image/jpeg'); } _binStr = _toBinary(dataUrl); if (_imgInfo) { _binStr = _imgInfo.stripHeaders(_binStr); if (_preserveHeaders) { // update dimensions info in exif if (_imgInfo.meta && _imgInfo.meta.exif) { _imgInfo.setExif({ PixelXDimension: this.width, PixelYDimension: this.height }); } // re-inject the headers _binStr = _imgInfo.writeHeaders(_binStr); } // will be re-created from fresh on next getInfo call _imgInfo.purge(); _imgInfo = null; } } _modified = false; return _binStr; }, destroy: function() { me = null; _purge.call(this); this.getRuntime().getShim().removeInstance(this.uid); } }); function _getImg() { if (!_canvas && !_img) { throw new x.ImageError(x.DOMException.INVALID_STATE_ERR); } return _canvas || _img; } function _toBinary(str) { return Encode.atob(str.substring(str.indexOf('base64,') + 7)); } function _toDataUrl(str, type) { return 'data:' + (type || '') + ';base64,' + Encode.btoa(str); } function _preload(str) { var comp = this; _img = new Image(); _img.onerror = function() { _purge.call(this); comp.trigger('error', x.ImageError.WRONG_FORMAT); }; _img.onload = function() { comp.trigger('load'); }; _img.src = str.substr(0, 5) == 'data:' ? str : _toDataUrl(str, _blob.type); } function _readAsDataUrl(file, callback) { var comp = this, fr; // use FileReader if it's available if (window.FileReader) { fr = new FileReader(); fr.onload = function() { callback(this.result); }; fr.onerror = function() { comp.trigger('error', x.ImageError.WRONG_FORMAT); }; fr.readAsDataURL(file); } else { return callback(file.getAsDataURL()); } } function _downsize(width, height, crop, preserveHeaders) { var self = this , scale , mathFn , x = 0 , y = 0 , img , destWidth , destHeight , orientation ; _preserveHeaders = preserveHeaders; // we will need to check this on export (see getAsBinaryString()) // take into account orientation tag orientation = (this.meta && this.meta.tiff && this.meta.tiff.Orientation) || 1; if (Basic.inArray(orientation, [5,6,7,8]) !== -1) { // values that require 90 degree rotation // swap dimensions var tmp = width; width = height; height = tmp; } img = _getImg(); // unify dimensions if (!crop) { scale = Math.min(width/img.width, height/img.height); } else { // one of the dimensions may exceed the actual image dimensions - we need to take the smallest value width = Math.min(width, img.width); height = Math.min(height, img.height); scale = Math.max(width/img.width, height/img.height); } // we only downsize here if (scale > 1 && !crop && preserveHeaders) { this.trigger('Resize'); return; } // prepare canvas if necessary if (!_canvas) { _canvas = document.createElement("canvas"); } // calculate dimensions of proportionally resized image destWidth = Math.round(img.width * scale); destHeight = Math.round(img.height * scale); // scale image and canvas if (crop) { _canvas.width = width; _canvas.height = height; // if dimensions of the resulting image still larger than canvas, center it if (destWidth > width) { x = Math.round((destWidth - width) / 2); } if (destHeight > height) { y = Math.round((destHeight - height) / 2); } } else { _canvas.width = destWidth; _canvas.height = destHeight; } // rotate if required, according to orientation tag if (!_preserveHeaders) { _rotateToOrientaion(_canvas.width, _canvas.height, orientation); } _drawToCanvas.call(this, img, _canvas, -x, -y, destWidth, destHeight); this.width = _canvas.width; this.height = _canvas.height; _modified = true; self.trigger('Resize'); } function _drawToCanvas(img, canvas, x, y, w, h) { if (Env.OS === 'iOS') { // avoid squish bug in iOS6 MegaPixel.renderTo(img, canvas, { width: w, height: h, x: x, y: y }); } else { var ctx = canvas.getContext('2d'); ctx.drawImage(img, x, y, w, h); } } /** * Transform canvas coordination according to specified frame size and orientation * Orientation value is from EXIF tag * @author Shinichi Tomita */ function _rotateToOrientaion(width, height, orientation) { switch (orientation) { case 5: case 6: case 7: case 8: _canvas.width = height; _canvas.height = width; break; default: _canvas.width = width; _canvas.height = height; } /** 1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side. 2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side. 3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side. 4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side. 5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top. 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top. 7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom. 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom. */ var ctx = _canvas.getContext('2d'); switch (orientation) { case 2: // horizontal flip ctx.translate(width, 0); ctx.scale(-1, 1); break; case 3: // 180 rotate left ctx.translate(width, height); ctx.rotate(Math.PI); break; case 4: // vertical flip ctx.translate(0, height); ctx.scale(1, -1); break; case 5: // vertical flip + 90 rotate right ctx.rotate(0.5 * Math.PI); ctx.scale(1, -1); break; case 6: // 90 rotate right ctx.rotate(0.5 * Math.PI); ctx.translate(0, -height); break; case 7: // horizontal flip + 90 rotate right ctx.rotate(0.5 * Math.PI); ctx.translate(width, -height); ctx.scale(-1, 1); break; case 8: // 90 rotate left ctx.rotate(-0.5 * Math.PI); ctx.translate(-width, 0); break; } } function _purge() { if (_imgInfo) { _imgInfo.purge(); _imgInfo = null; } _binStr = _img = _canvas = _blob = null; _modified = false; } } return (extensions.Image = HTML5Image); }); /** * Stub for moxie/runtime/flash/Runtime * @private */ define("moxie/runtime/flash/Runtime", [ ], function() { return {}; }); /** * Stub for moxie/runtime/silverlight/Runtime * @private */ define("moxie/runtime/silverlight/Runtime", [ ], function() { return {}; }); // Included from: src/javascript/runtime/html4/Runtime.js /** * Runtime.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /*global File:true */ /** Defines constructor for HTML4 runtime. @class moxie/runtime/html4/Runtime @private */ define("moxie/runtime/html4/Runtime", [ "moxie/core/utils/Basic", "moxie/core/Exceptions", "moxie/runtime/Runtime", "moxie/core/utils/Env" ], function(Basic, x, Runtime, Env) { var type = 'html4', extensions = {}; function Html4Runtime(options) { var I = this , Test = Runtime.capTest , True = Runtime.capTrue ; Runtime.call(this, options, type, { access_binary: Test(window.FileReader || window.File && File.getAsDataURL), access_image_binary: false, display_media: Test(extensions.Image && (Env.can('create_canvas') || Env.can('use_data_uri_over32kb'))), do_cors: false, drag_and_drop: false, filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest return (Env.browser === 'Chrome' && Env.verComp(Env.version, 28, '>=')) || (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) || (Env.browser === 'Safari' && Env.verComp(Env.version, 7, '>=')); }()), resize_image: function() { return extensions.Image && I.can('access_binary') && Env.can('create_canvas'); }, report_upload_progress: false, return_response_headers: false, return_response_type: function(responseType) { if (responseType === 'json' && !!window.JSON) { return true; } return !!~Basic.inArray(responseType, ['text', 'document', '']); }, return_status_code: function(code) { return !Basic.arrayDiff(code, [200, 404]); }, select_file: function() { return Env.can('use_fileinput'); }, select_multiple: false, send_binary_string: false, send_custom_headers: false, send_multipart: true, slice_blob: false, stream_upload: function() { return I.can('select_file'); }, summon_file_dialog: function() { // yeah... some dirty sniffing here... return I.can('select_file') && ( (Env.browser === 'Firefox' && Env.verComp(Env.version, 4, '>=')) || (Env.browser === 'Opera' && Env.verComp(Env.version, 12, '>=')) || (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) || !!~Basic.inArray(Env.browser, ['Chrome', 'Safari']) ); }, upload_filesize: True, use_http_method: function(methods) { return !Basic.arrayDiff(methods, ['GET', 'POST']); } }); Basic.extend(this, { init : function() { this.trigger("Init"); }, destroy: (function(destroy) { // extend default destroy method return function() { destroy.call(I); destroy = I = null; }; }(this.destroy)) }); Basic.extend(this.getShim(), extensions); } Runtime.addConstructor(type, Html4Runtime); return extensions; }); // Included from: src/javascript/runtime/html4/file/FileInput.js /** * FileInput.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html4/file/FileInput @private */ define("moxie/runtime/html4/file/FileInput", [ "moxie/runtime/html4/Runtime", "moxie/file/File", "moxie/core/utils/Basic", "moxie/core/utils/Dom", "moxie/core/utils/Events", "moxie/core/utils/Mime", "moxie/core/utils/Env" ], function(extensions, File, Basic, Dom, Events, Mime, Env) { function FileInput() { var _uid, _mimes = [], _options; function addInput() { var comp = this, I = comp.getRuntime(), shimContainer, browseButton, currForm, form, input, uid; uid = Basic.guid('uid_'); shimContainer = I.getShimContainer(); // we get new ref everytime to avoid memory leaks in IE if (_uid) { // move previous form out of the view currForm = Dom.get(_uid + '_form'); if (currForm) { Basic.extend(currForm.style, { top: '100%' }); } } // build form in DOM, since innerHTML version not able to submit file for some reason form = document.createElement('form'); form.setAttribute('id', uid + '_form'); form.setAttribute('method', 'post'); form.setAttribute('enctype', 'multipart/form-data'); form.setAttribute('encoding', 'multipart/form-data'); Basic.extend(form.style, { overflow: 'hidden', position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }); input = document.createElement('input'); input.setAttribute('id', uid); input.setAttribute('type', 'file'); input.setAttribute('name', _options.name || 'Filedata'); input.setAttribute('accept', _mimes.join(',')); Basic.extend(input.style, { fontSize: '999px', opacity: 0 }); form.appendChild(input); shimContainer.appendChild(form); // prepare file input to be placed underneath the browse_button element Basic.extend(input.style, { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }); if (Env.browser === 'IE' && Env.verComp(Env.version, 10, '<')) { Basic.extend(input.style, { filter : "progid:DXImageTransform.Microsoft.Alpha(opacity=0)" }); } input.onchange = function() { // there should be only one handler for this var file; if (!this.value) { return; } if (this.files) { // check if browser is fresh enough file = this.files[0]; // ignore empty files (IE10 for example hangs if you try to send them via XHR) if (file.size === 0) { form.parentNode.removeChild(form); return; } } else { file = { name: this.value }; } file = new File(I.uid, file); // clear event handler this.onchange = function() {}; addInput.call(comp); comp.files = [file]; // substitute all ids with file uids (consider file.uid read-only - we cannot do it the other way around) input.setAttribute('id', file.uid); form.setAttribute('id', file.uid + '_form'); comp.trigger('change'); input = form = null; }; // route click event to the input if (I.can('summon_file_dialog')) { browseButton = Dom.get(_options.browse_button); Events.removeEvent(browseButton, 'click', comp.uid); Events.addEvent(browseButton, 'click', function(e) { if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file] input.click(); } e.preventDefault(); }, comp.uid); } _uid = uid; shimContainer = currForm = browseButton = null; } Basic.extend(this, { init: function(options) { var comp = this, I = comp.getRuntime(), shimContainer; // figure out accept string _options = options; _mimes = options.accept.mimes || Mime.extList2mimes(options.accept, I.can('filter_by_extension')); shimContainer = I.getShimContainer(); (function() { var browseButton, zIndex, top; browseButton = Dom.get(options.browse_button); // Route click event to the input[type=file] element for browsers that support such behavior if (I.can('summon_file_dialog')) { if (Dom.getStyle(browseButton, 'position') === 'static') { browseButton.style.position = 'relative'; } zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1; browseButton.style.zIndex = zIndex; shimContainer.style.zIndex = zIndex - 1; } /* Since we have to place input[type=file] on top of the browse_button for some browsers, browse_button loses interactivity, so we restore it here */ top = I.can('summon_file_dialog') ? browseButton : shimContainer; Events.addEvent(top, 'mouseover', function() { comp.trigger('mouseenter'); }, comp.uid); Events.addEvent(top, 'mouseout', function() { comp.trigger('mouseleave'); }, comp.uid); Events.addEvent(top, 'mousedown', function() { comp.trigger('mousedown'); }, comp.uid); Events.addEvent(Dom.get(options.container), 'mouseup', function() { comp.trigger('mouseup'); }, comp.uid); browseButton = null; }()); addInput.call(this); shimContainer = null; // trigger ready event asynchronously comp.trigger({ type: 'ready', async: true }); }, disable: function(state) { var input; if ((input = Dom.get(_uid))) { input.disabled = !!state; } }, destroy: function() { var I = this.getRuntime() , shim = I.getShim() , shimContainer = I.getShimContainer() ; Events.removeAllEvents(shimContainer, this.uid); Events.removeAllEvents(_options && Dom.get(_options.container), this.uid); Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid); if (shimContainer) { shimContainer.innerHTML = ''; } shim.removeInstance(this.uid); _uid = _mimes = _options = shimContainer = shim = null; } }); } return (extensions.FileInput = FileInput); }); // Included from: src/javascript/runtime/html4/file/FileReader.js /** * FileReader.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html4/file/FileReader @private */ define("moxie/runtime/html4/file/FileReader", [ "moxie/runtime/html4/Runtime", "moxie/runtime/html5/file/FileReader" ], function(extensions, FileReader) { return (extensions.FileReader = FileReader); }); // Included from: src/javascript/runtime/html4/xhr/XMLHttpRequest.js /** * XMLHttpRequest.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html4/xhr/XMLHttpRequest @private */ define("moxie/runtime/html4/xhr/XMLHttpRequest", [ "moxie/runtime/html4/Runtime", "moxie/core/utils/Basic", "moxie/core/utils/Dom", "moxie/core/utils/Url", "moxie/core/Exceptions", "moxie/core/utils/Events", "moxie/file/Blob", "moxie/xhr/FormData" ], function(extensions, Basic, Dom, Url, x, Events, Blob, FormData) { function XMLHttpRequest() { var _status, _response, _iframe; function cleanup(cb) { var target = this, uid, form, inputs, i, hasFile = false; if (!_iframe) { return; } uid = _iframe.id.replace(/_iframe$/, ''); form = Dom.get(uid + '_form'); if (form) { inputs = form.getElementsByTagName('input'); i = inputs.length; while (i--) { switch (inputs[i].getAttribute('type')) { case 'hidden': inputs[i].parentNode.removeChild(inputs[i]); break; case 'file': hasFile = true; // flag the case for later break; } } inputs = []; if (!hasFile) { // we need to keep the form for sake of possible retries form.parentNode.removeChild(form); } form = null; } // without timeout, request is marked as canceled (in console) setTimeout(function() { Events.removeEvent(_iframe, 'load', target.uid); if (_iframe.parentNode) { // #382 _iframe.parentNode.removeChild(_iframe); } // check if shim container has any other children, if - not, remove it as well var shimContainer = target.getRuntime().getShimContainer(); if (!shimContainer.children.length) { shimContainer.parentNode.removeChild(shimContainer); } shimContainer = _iframe = null; cb(); }, 1); } Basic.extend(this, { send: function(meta, data) { var target = this, I = target.getRuntime(), uid, form, input, blob; _status = _response = null; function createIframe() { var container = I.getShimContainer() || document.body , temp = document.createElement('div') ; // IE 6 won't be able to set the name using setAttribute or iframe.name temp.innerHTML = ''; _iframe = temp.firstChild; container.appendChild(_iframe); /* _iframe.onreadystatechange = function() { console.info(_iframe.readyState); };*/ Events.addEvent(_iframe, 'load', function() { // _iframe.onload doesn't work in IE lte 8 var el; try { el = _iframe.contentWindow.document || _iframe.contentDocument || window.frames[_iframe.id].document; // try to detect some standard error pages if (/^4(0[0-9]|1[0-7]|2[2346])\s/.test(el.title)) { // test if title starts with 4xx HTTP error _status = el.title.replace(/^(\d+).*$/, '$1'); } else { _status = 200; // get result _response = Basic.trim(el.body.innerHTML); // we need to fire these at least once target.trigger({ type: 'progress', loaded: _response.length, total: _response.length }); if (blob) { // if we were uploading a file target.trigger({ type: 'uploadprogress', loaded: blob.size || 1025, total: blob.size || 1025 }); } } } catch (ex) { if (Url.hasSameOrigin(meta.url)) { // if response is sent with error code, iframe in IE gets redirected to res://ieframe.dll/http_x.htm // which obviously results to cross domain error (wtf?) _status = 404; } else { cleanup.call(target, function() { target.trigger('error'); }); return; } } cleanup.call(target, function() { target.trigger('load'); }); }, target.uid); } // end createIframe // prepare data to be sent and convert if required if (data instanceof FormData && data.hasBlob()) { blob = data.getBlob(); uid = blob.uid; input = Dom.get(uid); form = Dom.get(uid + '_form'); if (!form) { throw new x.DOMException(x.DOMException.NOT_FOUND_ERR); } } else { uid = Basic.guid('uid_'); form = document.createElement('form'); form.setAttribute('id', uid + '_form'); form.setAttribute('method', meta.method); form.setAttribute('enctype', 'multipart/form-data'); form.setAttribute('encoding', 'multipart/form-data'); I.getShimContainer().appendChild(form); } // set upload target form.setAttribute('target', uid + '_iframe'); if (data instanceof FormData) { data.each(function(value, name) { if (value instanceof Blob) { if (input) { input.setAttribute('name', name); } } else { var hidden = document.createElement('input'); Basic.extend(hidden, { type : 'hidden', name : name, value : value }); // make sure that input[type="file"], if it's there, comes last if (input) { form.insertBefore(hidden, input); } else { form.appendChild(hidden); } } }); } // set destination url form.setAttribute("action", meta.url); createIframe(); form.submit(); target.trigger('loadstart'); }, getStatus: function() { return _status; }, getResponse: function(responseType) { if ('json' === responseType) { // strip off
..
tags that might be enclosing the response if (Basic.typeOf(_response) === 'string' && !!window.JSON) { try { return JSON.parse(_response.replace(/^\s*]*>/, '').replace(/<\/pre>\s*$/, '')); } catch (ex) { return null; } } } else if ('document' === responseType) { } return _response; }, abort: function() { var target = this; if (_iframe && _iframe.contentWindow) { if (_iframe.contentWindow.stop) { // FireFox/Safari/Chrome _iframe.contentWindow.stop(); } else if (_iframe.contentWindow.document.execCommand) { // IE _iframe.contentWindow.document.execCommand('Stop'); } else { _iframe.src = "about:blank"; } } cleanup.call(this, function() { // target.dispatchEvent('readystatechange'); target.dispatchEvent('abort'); }); } }); } return (extensions.XMLHttpRequest = XMLHttpRequest); }); // Included from: src/javascript/runtime/html4/image/Image.js /** * Image.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** @class moxie/runtime/html4/image/Image @private */ define("moxie/runtime/html4/image/Image", [ "moxie/runtime/html4/Runtime", "moxie/runtime/html5/image/Image" ], function(extensions, Image) { return (extensions.Image = Image); }); expose(["moxie/core/utils/Basic","moxie/core/utils/Env","moxie/core/I18n","moxie/core/utils/Mime","moxie/core/utils/Dom","moxie/core/Exceptions","moxie/core/EventTarget","moxie/runtime/Runtime","moxie/runtime/RuntimeClient","moxie/file/FileInput","moxie/core/utils/Encode","moxie/file/Blob","moxie/file/File","moxie/file/FileDrop","moxie/file/FileReader","moxie/core/utils/Url","moxie/runtime/RuntimeTarget","moxie/file/FileReaderSync","moxie/xhr/FormData","moxie/xhr/XMLHttpRequest","moxie/runtime/Transporter","moxie/image/Image","moxie/core/utils/Events"]); })(this); /** * o.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /*global moxie:true */ /** Globally exposed namespace with the most frequently used public classes and handy methods. @class o @static @private */ (function(exports) { "use strict"; var o = {}, inArray = exports.moxie.core.utils.Basic.inArray; // directly add some public classes // (we do it dynamically here, since for custom builds we cannot know beforehand what modules were included) (function addAlias(ns) { var name, itemType; for (name in ns) { itemType = typeof(ns[name]); if (itemType === 'object' && !~inArray(name, ['Exceptions', 'Env', 'Mime'])) { addAlias(ns[name]); } else if (itemType === 'function') { o[name] = ns[name]; } } })(exports.moxie); // add some manually o.Env = exports.moxie.core.utils.Env; o.Mime = exports.moxie.core.utils.Mime; o.Exceptions = exports.moxie.core.Exceptions; // expose globally exports.mOxie = o; if (!exports.o) { exports.o = o; } return o; })(this); ; /** * Plupload - multi-runtime File Uploader * v2.1.9 * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing * * Date: 2016-05-15 */ /** * Plupload.js * * Copyright 2013, Moxiecode Systems AB * Released under GPL License. * * License: http://www.plupload.com/license * Contributing: http://www.plupload.com/contributing */ /** * Modified for WordPress, Silverlight and Flash runtimes support was removed. * See https://core.trac.wordpress.org/ticket/41755. */ /*global mOxie:true */ ;(function(window, o, undef) { var delay = window.setTimeout , fileFilters = {} ; // convert plupload features to caps acceptable by mOxie function normalizeCaps(settings) { var features = settings.required_features, caps = {}; function resolve(feature, value, strict) { // Feature notation is deprecated, use caps (this thing here is required for backward compatibility) var map = { chunks: 'slice_blob', jpgresize: 'send_binary_string', pngresize: 'send_binary_string', progress: 'report_upload_progress', multi_selection: 'select_multiple', dragdrop: 'drag_and_drop', drop_element: 'drag_and_drop', headers: 'send_custom_headers', urlstream_upload: 'send_binary_string', canSendBinary: 'send_binary', triggerDialog: 'summon_file_dialog' }; if (map[feature]) { caps[map[feature]] = value; } else if (!strict) { caps[feature] = value; } } if (typeof(features) === 'string') { plupload.each(features.split(/\s*,\s*/), function(feature) { resolve(feature, true); }); } else if (typeof(features) === 'object') { plupload.each(features, function(value, feature) { resolve(feature, value); }); } else if (features === true) { // check settings for required features if (settings.chunk_size > 0) { caps.slice_blob = true; } if (settings.resize.enabled || !settings.multipart) { caps.send_binary_string = true; } plupload.each(settings, function(value, feature) { resolve(feature, !!value, true); // strict check }); } // WP: only html runtimes. settings.runtimes = 'html5,html4'; return caps; } /** * @module plupload * @static */ var plupload = { /** * Plupload version will be replaced on build. * * @property VERSION * @for Plupload * @static * @final */ VERSION : '2.1.9', /** * The state of the queue before it has started and after it has finished * * @property STOPPED * @static * @final */ STOPPED : 1, /** * Upload process is running * * @property STARTED * @static * @final */ STARTED : 2, /** * File is queued for upload * * @property QUEUED * @static * @final */ QUEUED : 1, /** * File is being uploaded * * @property UPLOADING * @static * @final */ UPLOADING : 2, /** * File has failed to be uploaded * * @property FAILED * @static * @final */ FAILED : 4, /** * File has been uploaded successfully * * @property DONE * @static * @final */ DONE : 5, // Error constants used by the Error event /** * Generic error for example if an exception is thrown inside Silverlight. * * @property GENERIC_ERROR * @static * @final */ GENERIC_ERROR : -100, /** * HTTP transport error. For example if the server produces a HTTP status other than 200. * * @property HTTP_ERROR * @static * @final */ HTTP_ERROR : -200, /** * Generic I/O error. For example if it wasn't possible to open the file stream on local machine. * * @property IO_ERROR * @static * @final */ IO_ERROR : -300, /** * @property SECURITY_ERROR * @static * @final */ SECURITY_ERROR : -400, /** * Initialization error. Will be triggered if no runtime was initialized. * * @property INIT_ERROR * @static * @final */ INIT_ERROR : -500, /** * File size error. If the user selects a file that is too large it will be blocked and an error of this type will be triggered. * * @property FILE_SIZE_ERROR * @static * @final */ FILE_SIZE_ERROR : -600, /** * File extension error. If the user selects a file that isn't valid according to the filters setting. * * @property FILE_EXTENSION_ERROR * @static * @final */ FILE_EXTENSION_ERROR : -601, /** * Duplicate file error. If prevent_duplicates is set to true and user selects the same file again. * * @property FILE_DUPLICATE_ERROR * @static * @final */ FILE_DUPLICATE_ERROR : -602, /** * Runtime will try to detect if image is proper one. Otherwise will throw this error. * * @property IMAGE_FORMAT_ERROR * @static * @final */ IMAGE_FORMAT_ERROR : -700, /** * While working on files runtime may run out of memory and will throw this error. * * @since 2.1.2 * @property MEMORY_ERROR * @static * @final */ MEMORY_ERROR : -701, /** * Each runtime has an upper limit on a dimension of the image it can handle. If bigger, will throw this error. * * @property IMAGE_DIMENSIONS_ERROR * @static * @final */ IMAGE_DIMENSIONS_ERROR : -702, /** * Mime type lookup table. * * @property mimeTypes * @type Object * @final */ mimeTypes : o.mimes, /** * In some cases sniffing is the only way around :( */ ua: o.ua, /** * Gets the true type of the built-in object (better version of typeof). * @credits Angus Croll (http://javascriptweblog.wordpress.com/) * * @method typeOf * @static * @param {Object} o Object to check. * @return {String} Object [[Class]] */ typeOf: o.typeOf, /** * Extends the specified object with another object. * * @method extend * @static * @param {Object} target Object to extend. * @param {Object..} obj Multiple objects to extend with. * @return {Object} Same as target, the extended object. */ extend : o.extend, /** * Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers. * The only way a user would be able to get the same ID is if the two persons at the same exact millisecond manages * to get 5 the same random numbers between 0-65535 it also uses a counter so each call will be guaranteed to be page unique. * It's more probable for the earth to be hit with an asteriod. You can also if you want to be 100% sure set the plupload.guidPrefix property * to an user unique key. * * @method guid * @static * @return {String} Virtually unique id. */ guid : o.guid, /** * Get array of DOM Elements by their ids. * * @method get * @param {String} id Identifier of the DOM Element * @return {Array} */ getAll : function get(ids) { var els = [], el; if (plupload.typeOf(ids) !== 'array') { ids = [ids]; } var i = ids.length; while (i--) { el = plupload.get(ids[i]); if (el) { els.push(el); } } return els.length ? els : null; }, /** Get DOM element by id @method get @param {String} id Identifier of the DOM Element @return {Node} */ get: o.get, /** * Executes the callback function for each item in array/object. If you return false in the * callback it will break the loop. * * @method each * @static * @param {Object} obj Object to iterate. * @param {function} callback Callback function to execute for each item. */ each : o.each, /** * Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields. * * @method getPos * @static * @param {Element} node HTML element or element id to get x, y position from. * @param {Element} root Optional root element to stop calculations at. * @return {object} Absolute position of the specified element object with x, y fields. */ getPos : o.getPos, /** * Returns the size of the specified node in pixels. * * @method getSize * @static * @param {Node} node Node to get the size of. * @return {Object} Object with a w and h property. */ getSize : o.getSize, /** * Encodes the specified string. * * @method xmlEncode * @static * @param {String} s String to encode. * @return {String} Encoded string. */ xmlEncode : function(str) { var xmlEncodeChars = {'<' : 'lt', '>' : 'gt', '&' : 'amp', '"' : 'quot', '\'' : '#39'}, xmlEncodeRegExp = /[<>&\"\']/g; return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) { return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr; }) : str; }, /** * Forces anything into an array. * * @method toArray * @static * @param {Object} obj Object with length field. * @return {Array} Array object containing all items. */ toArray : o.toArray, /** * Find an element in array and return its index if present, otherwise return -1. * * @method inArray * @static * @param {mixed} needle Element to find * @param {Array} array * @return {Int} Index of the element, or -1 if not found */ inArray : o.inArray, /** * Extends the language pack object with new items. * * @method addI18n * @static * @param {Object} pack Language pack items to add. * @return {Object} Extended language pack object. */ addI18n : o.addI18n, /** * Translates the specified string by checking for the english string in the language pack lookup. * * @method translate * @static * @param {String} str String to look for. * @return {String} Translated string or the input string if it wasn't found. */ translate : o.translate, /** * Checks if object is empty. * * @method isEmptyObj * @static * @param {Object} obj Object to check. * @return {Boolean} */ isEmptyObj : o.isEmptyObj, /** * Checks if specified DOM element has specified class. * * @method hasClass * @static * @param {Object} obj DOM element like object to add handler to. * @param {String} name Class name */ hasClass : o.hasClass, /** * Adds specified className to specified DOM element. * * @method addClass * @static * @param {Object} obj DOM element like object to add handler to. * @param {String} name Class name */ addClass : o.addClass, /** * Removes specified className from specified DOM element. * * @method removeClass * @static * @param {Object} obj DOM element like object to add handler to. * @param {String} name Class name */ removeClass : o.removeClass, /** * Returns a given computed style of a DOM element. * * @method getStyle * @static * @param {Object} obj DOM element like object. * @param {String} name Style you want to get from the DOM element */ getStyle : o.getStyle, /** * Adds an event handler to the specified object and store reference to the handler * in objects internal Plupload registry (@see removeEvent). * * @method addEvent * @static * @param {Object} obj DOM element like object to add handler to. * @param {String} name Name to add event listener to. * @param {Function} callback Function to call when event occurs. * @param {String} (optional) key that might be used to add specifity to the event record. */ addEvent : o.addEvent, /** * Remove event handler from the specified object. If third argument (callback) * is not specified remove all events with the specified name. * * @method removeEvent * @static * @param {Object} obj DOM element to remove event listener(s) from. * @param {String} name Name of event listener to remove. * @param {Function|String} (optional) might be a callback or unique key to match. */ removeEvent: o.removeEvent, /** * Remove all kind of events from the specified object * * @method removeAllEvents * @static * @param {Object} obj DOM element to remove event listeners from. * @param {String} (optional) unique key to match, when removing events. */ removeAllEvents: o.removeAllEvents, /** * Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _. * * @method cleanName * @static * @param {String} s String to clean up. * @return {String} Cleaned string. */ cleanName : function(name) { var i, lookup; // Replace diacritics lookup = [ /[\300-\306]/g, 'A', /[\340-\346]/g, 'a', /\307/g, 'C', /\347/g, 'c', /[\310-\313]/g, 'E', /[\350-\353]/g, 'e', /[\314-\317]/g, 'I', /[\354-\357]/g, 'i', /\321/g, 'N', /\361/g, 'n', /[\322-\330]/g, 'O', /[\362-\370]/g, 'o', /[\331-\334]/g, 'U', /[\371-\374]/g, 'u' ]; for (i = 0; i < lookup.length; i += 2) { name = name.replace(lookup[i], lookup[i + 1]); } // Replace whitespace name = name.replace(/\s+/g, '_'); // Remove anything else name = name.replace(/[^a-z0-9_\-\.]+/gi, ''); return name; }, /** * Builds a full url out of a base URL and an object with items to append as query string items. * * @method buildUrl * @static * @param {String} url Base URL to append query string items to. * @param {Object} items Name/value object to serialize as a querystring. * @return {String} String with url + serialized query string items. */ buildUrl : function(url, items) { var query = ''; plupload.each(items, function(value, name) { query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value); }); if (query) { url += (url.indexOf('?') > 0 ? '&' : '?') + query; } return url; }, /** * Formats the specified number as a size string for example 1024 becomes 1 KB. * * @method formatSize * @static * @param {Number} size Size to format as string. * @return {String} Formatted size string. */ formatSize : function(size) { if (size === undef || /\D/.test(size)) { return plupload.translate('N/A'); } function round(num, precision) { return Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision); } var boundary = Math.pow(1024, 4); // TB if (size > boundary) { return round(size / boundary, 1) + " " + plupload.translate('tb'); } // GB if (size > (boundary/=1024)) { return round(size / boundary, 1) + " " + plupload.translate('gb'); } // MB if (size > (boundary/=1024)) { return round(size / boundary, 1) + " " + plupload.translate('mb'); } // KB if (size > 1024) { return Math.round(size / 1024) + " " + plupload.translate('kb'); } return size + " " + plupload.translate('b'); }, /** * Parses the specified size string into a byte value. For example 10kb becomes 10240. * * @method parseSize * @static * @param {String|Number} size String to parse or number to just pass through. * @return {Number} Size in bytes. */ parseSize : o.parseSizeStr, /** * A way to predict what runtime will be choosen in the current environment with the * specified settings. * * @method predictRuntime * @static * @param {Object|String} config Plupload settings to check * @param {String} [runtimes] Comma-separated list of runtimes to check against * @return {String} Type of compatible runtime */ predictRuntime : function(config, runtimes) { var up, runtime; up = new plupload.Uploader(config); runtime = o.Runtime.thatCan(up.getOption().required_features, runtimes || config.runtimes); up.destroy(); return runtime; }, /** * Registers a filter that will be executed for each file added to the queue. * If callback returns false, file will not be added. * * Callback receives two arguments: a value for the filter as it was specified in settings.filters * and a file to be filtered. Callback is executed in the context of uploader instance. * * @method addFileFilter * @static * @param {String} name Name of the filter by which it can be referenced in settings.filters * @param {String} cb Callback - the actual routine that every added file must pass */ addFileFilter: function(name, cb) { fileFilters[name] = cb; } }; plupload.addFileFilter('mime_types', function(filters, file, cb) { if (filters.length && !filters.regexp.test(file.name)) { this.trigger('Error', { code : plupload.FILE_EXTENSION_ERROR, message : plupload.translate('File extension error.'), file : file }); cb(false); } else { cb(true); } }); plupload.addFileFilter('max_file_size', function(maxSize, file, cb) { var undef; maxSize = plupload.parseSize(maxSize); // Invalid file size if (file.size !== undef && maxSize && file.size > maxSize) { this.trigger('Error', { code : plupload.FILE_SIZE_ERROR, message : plupload.translate('File size error.'), file : file }); cb(false); } else { cb(true); } }); plupload.addFileFilter('prevent_duplicates', function(value, file, cb) { if (value) { var ii = this.files.length; while (ii--) { // Compare by name and size (size might be 0 or undefined, but still equivalent for both) if (file.name === this.files[ii].name && file.size === this.files[ii].size) { this.trigger('Error', { code : plupload.FILE_DUPLICATE_ERROR, message : plupload.translate('Duplicate file error.'), file : file }); cb(false); return; } } } cb(true); }); /** @class Uploader @constructor @param {Object} settings For detailed information about each option check documentation. @param {String|DOMElement} settings.browse_button id of the DOM element or DOM element itself to use as file dialog trigger. @param {String} settings.url URL of the server-side upload handler. @param {Number|String} [settings.chunk_size=0] Chunk size in bytes to slice the file into. Shorcuts with b, kb, mb, gb, tb suffixes also supported. `e.g. 204800 or "204800b" or "200kb"`. By default - disabled. @param {Boolean} [settings.send_chunk_number=true] Whether to send chunks and chunk numbers, or total and offset bytes. @param {String|DOMElement} [settings.container] id of the DOM element or DOM element itself that will be used to wrap uploader structures. Defaults to immediate parent of the `browse_button` element. @param {String|DOMElement} [settings.drop_element] id of the DOM element or DOM element itself to use as a drop zone for Drag-n-Drop. @param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message. @param {Object} [settings.filters={}] Set of file type filters. @param {Array} [settings.filters.mime_types=[]] List of file types to accept, each one defined by title and list of extensions. `e.g. {title : "Image files", extensions : "jpg,jpeg,gif,png"}`. Dispatches `plupload.FILE_EXTENSION_ERROR` @param {String|Number} [settings.filters.max_file_size=0] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`. @param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`. @param {String} [settings.flash_swf_url] URL of the Flash swf. (Not used in WordPress) @param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs. @param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event. @param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message. @param {Object} [settings.multipart_params] Hash of key/value pairs to send with every file upload. @param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog. @param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess. @param {Object} [settings.resize] Enable resizng of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}` @param {Number} [settings.resize.width] If image is bigger, it will be resized. @param {Number} [settings.resize.height] If image is bigger, it will be resized. @param {Number} [settings.resize.quality=90] Compression quality for jpegs (1-100). @param {Boolean} [settings.resize.crop=false] Whether to crop images to exact dimensions. By default they will be resized proportionally. @param {String} [settings.runtimes="html5,html4"] Comma separated list of runtimes, that Plupload will try in turn, moving to the next if previous fails. @param {String} [settings.silverlight_xap_url] URL of the Silverlight xap. (Not used in WordPress) @param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files. @param {Boolean} [settings.send_file_name=true] Whether to send file name as additional argument - 'name' (required for chunked uploads and some other cases where file name cannot be sent via normal ways). */ plupload.Uploader = function(options) { /** Fires when the current RunTime has been initialized. @event Init @param {plupload.Uploader} uploader Uploader instance sending the event. */ /** Fires after the init event incase you need to perform actions there. @event PostInit @param {plupload.Uploader} uploader Uploader instance sending the event. */ /** Fires when the option is changed in via uploader.setOption(). @event OptionChanged @since 2.1 @param {plupload.Uploader} uploader Uploader instance sending the event. @param {String} name Name of the option that was changed @param {Mixed} value New value for the specified option @param {Mixed} oldValue Previous value of the option */ /** Fires when the silverlight/flash or other shim needs to move. @event Refresh @param {plupload.Uploader} uploader Uploader instance sending the event. */ /** Fires when the overall state is being changed for the upload queue. @event StateChanged @param {plupload.Uploader} uploader Uploader instance sending the event. */ /** Fires when browse_button is clicked and browse dialog shows. @event Browse @since 2.1.2 @param {plupload.Uploader} uploader Uploader instance sending the event. */ /** Fires for every filtered file before it is added to the queue. @event FileFiltered @since 2.1 @param {plupload.Uploader} uploader Uploader instance sending the event. @param {plupload.File} file Another file that has to be added to the queue. */ /** Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance. @event QueueChanged @param {plupload.Uploader} uploader Uploader instance sending the event. */ /** Fires after files were filtered and added to the queue. @event FilesAdded @param {plupload.Uploader} uploader Uploader instance sending the event. @param {Array} files Array of file objects that were added to queue by the user. */ /** Fires when file is removed from the queue. @event FilesRemoved @param {plupload.Uploader} uploader Uploader instance sending the event. @param {Array} files Array of files that got removed. */ /** Fires just before a file is uploaded. Can be used to cancel the upload for the specified file by returning false from the handler. @event BeforeUpload @param {plupload.Uploader} uploader Uploader instance sending the event. @param {plupload.File} file File to be uploaded. */ /** Fires when a file is to be uploaded by the runtime. @event UploadFile @param {plupload.Uploader} uploader Uploader instance sending the event. @param {plupload.File} file File to be uploaded. */ /** Fires while a file is being uploaded. Use this event to update the current file upload progress. @event UploadProgress @param {plupload.Uploader} uploader Uploader instance sending the event. @param {plupload.File} file File that is currently being uploaded. */ /** Fires when file chunk is uploaded. @event ChunkUploaded @param {plupload.Uploader} uploader Uploader instance sending the event. @param {plupload.File} file File that the chunk was uploaded for. @param {Object} result Object with response properties. @param {Number} result.offset The amount of bytes the server has received so far, including this chunk. @param {Number} result.total The size of the file. @param {String} result.response The response body sent by the server. @param {Number} result.status The HTTP status code sent by the server. @param {String} result.responseHeaders All the response headers as a single string. */ /** Fires when a file is successfully uploaded. @event FileUploaded @param {plupload.Uploader} uploader Uploader instance sending the event. @param {plupload.File} file File that was uploaded. @param {Object} result Object with response properties. @param {String} result.response The response body sent by the server. @param {Number} result.status The HTTP status code sent by the server. @param {String} result.responseHeaders All the response headers as a single string. */ /** Fires when all files in a queue are uploaded. @event UploadComplete @param {plupload.Uploader} uploader Uploader instance sending the event. @param {Array} files Array of file objects that was added to queue/selected by the user. */ /** Fires when a error occurs. @event Error @param {plupload.Uploader} uploader Uploader instance sending the event. @param {Object} error Contains code, message and sometimes file and other details. @param {Number} error.code The plupload error code. @param {String} error.message Description of the error (uses i18n). */ /** Fires when destroy method is called. @event Destroy @param {plupload.Uploader} uploader Uploader instance sending the event. */ var uid = plupload.guid() , settings , files = [] , preferred_caps = {} , fileInputs = [] , fileDrops = [] , startTime , total , disabled = false , xhr ; // Private methods function uploadNext() { var file, count = 0, i; if (this.state == plupload.STARTED) { // Find first QUEUED file for (i = 0; i < files.length; i++) { if (!file && files[i].status == plupload.QUEUED) { file = files[i]; if (this.trigger("BeforeUpload", file)) { file.status = plupload.UPLOADING; this.trigger("UploadFile", file); } } else { count++; } } // All files are DONE or FAILED if (count == files.length) { if (this.state !== plupload.STOPPED) { this.state = plupload.STOPPED; this.trigger("StateChanged"); } this.trigger("UploadComplete", files); } } } function calcFile(file) { file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100; calc(); } function calc() { var i, file; // Reset stats total.reset(); // Check status, size, loaded etc on all files for (i = 0; i < files.length; i++) { file = files[i]; if (file.size !== undef) { // We calculate totals based on original file size total.size += file.origSize; // Since we cannot predict file size after resize, we do opposite and // interpolate loaded amount to match magnitude of total total.loaded += file.loaded * file.origSize / file.size; } else { total.size = undef; } if (file.status == plupload.DONE) { total.uploaded++; } else if (file.status == plupload.FAILED) { total.failed++; } else { total.queued++; } } // If we couldn't calculate a total file size then use the number of files to calc percent if (total.size === undef) { total.percent = files.length > 0 ? Math.ceil(total.uploaded / files.length * 100) : 0; } else { total.bytesPerSec = Math.ceil(total.loaded / ((+new Date() - startTime || 1) / 1000.0)); total.percent = total.size > 0 ? Math.ceil(total.loaded / total.size * 100) : 0; } } function getRUID() { var ctrl = fileInputs[0] || fileDrops[0]; if (ctrl) { return ctrl.getRuntime().uid; } return false; } function runtimeCan(file, cap) { if (file.ruid) { var info = o.Runtime.getInfo(file.ruid); if (info) { return info.can(cap); } } return false; } function bindEventListeners() { this.bind('FilesAdded FilesRemoved', function(up) { up.trigger('QueueChanged'); up.refresh(); }); this.bind('CancelUpload', onCancelUpload); this.bind('BeforeUpload', onBeforeUpload); this.bind('UploadFile', onUploadFile); this.bind('UploadProgress', onUploadProgress); this.bind('StateChanged', onStateChanged); this.bind('QueueChanged', calc); this.bind('Error', onError); this.bind('FileUploaded', onFileUploaded); this.bind('Destroy', onDestroy); } function initControls(settings, cb) { var self = this, inited = 0, queue = []; // common settings var options = { runtime_order: settings.runtimes, required_caps: settings.required_features, preferred_caps: preferred_caps }; // add runtime specific options if any plupload.each(settings.runtimes.split(/\s*,\s*/), function(runtime) { if (settings[runtime]) { options[runtime] = settings[runtime]; } }); // initialize file pickers - there can be many if (settings.browse_button) { plupload.each(settings.browse_button, function(el) { queue.push(function(cb) { var fileInput = new o.FileInput(plupload.extend({}, options, { accept: settings.filters.mime_types, name: settings.file_data_name, multiple: settings.multi_selection, container: settings.container, browse_button: el })); fileInput.onready = function() { var info = o.Runtime.getInfo(this.ruid); // for backward compatibility o.extend(self.features, { chunks: info.can('slice_blob'), multipart: info.can('send_multipart'), multi_selection: info.can('select_multiple') }); inited++; fileInputs.push(this); cb(); }; fileInput.onchange = function() { self.addFile(this.files); }; fileInput.bind('mouseenter mouseleave mousedown mouseup', function(e) { if (!disabled) { if (settings.browse_button_hover) { if ('mouseenter' === e.type) { o.addClass(el, settings.browse_button_hover); } else if ('mouseleave' === e.type) { o.removeClass(el, settings.browse_button_hover); } } if (settings.browse_button_active) { if ('mousedown' === e.type) { o.addClass(el, settings.browse_button_active); } else if ('mouseup' === e.type) { o.removeClass(el, settings.browse_button_active); } } } }); fileInput.bind('mousedown', function() { self.trigger('Browse'); }); fileInput.bind('error runtimeerror', function() { fileInput = null; cb(); }); fileInput.init(); }); }); } // initialize drop zones if (settings.drop_element) { plupload.each(settings.drop_element, function(el) { queue.push(function(cb) { var fileDrop = new o.FileDrop(plupload.extend({}, options, { drop_zone: el })); fileDrop.onready = function() { var info = o.Runtime.getInfo(this.ruid); // for backward compatibility o.extend(self.features, { chunks: info.can('slice_blob'), multipart: info.can('send_multipart'), dragdrop: info.can('drag_and_drop') }); inited++; fileDrops.push(this); cb(); }; fileDrop.ondrop = function() { self.addFile(this.files); }; fileDrop.bind('error runtimeerror', function() { fileDrop = null; cb(); }); fileDrop.init(); }); }); } o.inSeries(queue, function() { if (typeof(cb) === 'function') { cb(inited); } }); } function resizeImage(blob, params, cb) { var img = new o.Image(); try { img.onload = function() { // no manipulation required if... if (params.width > this.width && params.height > this.height && params.quality === undef && params.preserve_headers && !params.crop ) { this.destroy(); return cb(blob); } // otherwise downsize img.downsize(params.width, params.height, params.crop, params.preserve_headers); }; img.onresize = function() { cb(this.getAsBlob(blob.type, params.quality)); this.destroy(); }; img.onerror = function() { cb(blob); }; img.load(blob); } catch(ex) { cb(blob); } } function setOption(option, value, init) { var self = this, reinitRequired = false; function _setOption(option, value, init) { var oldValue = settings[option]; switch (option) { case 'max_file_size': if (option === 'max_file_size') { settings.max_file_size = settings.filters.max_file_size = value; } break; case 'chunk_size': if (value = plupload.parseSize(value)) { settings[option] = value; settings.send_file_name = true; } break; case 'multipart': settings[option] = value; if (!value) { settings.send_file_name = true; } break; case 'unique_names': settings[option] = value; if (value) { settings.send_file_name = true; } break; case 'filters': // for sake of backward compatibility if (plupload.typeOf(value) === 'array') { value = { mime_types: value }; } if (init) { plupload.extend(settings.filters, value); } else { settings.filters = value; } // if file format filters are being updated, regenerate the matching expressions if (value.mime_types) { settings.filters.mime_types.regexp = (function(filters) { var extensionsRegExp = []; plupload.each(filters, function(filter) { plupload.each(filter.extensions.split(/,/), function(ext) { if (/^\s*\*\s*$/.test(ext)) { extensionsRegExp.push('\\.*'); } else { extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&')); } }); }); return new RegExp('(' + extensionsRegExp.join('|') + ')$', 'i'); }(settings.filters.mime_types)); } break; case 'resize': if (init) { plupload.extend(settings.resize, value, { enabled: true }); } else { settings.resize = value; } break; case 'prevent_duplicates': settings.prevent_duplicates = settings.filters.prevent_duplicates = !!value; break; // options that require reinitialisation case 'container': case 'browse_button': case 'drop_element': value = 'container' === option ? plupload.get(value) : plupload.getAll(value) ; case 'runtimes': case 'multi_selection': settings[option] = value; if (!init) { reinitRequired = true; } break; default: settings[option] = value; } if (!init) { self.trigger('OptionChanged', option, value, oldValue); } } if (typeof(option) === 'object') { plupload.each(option, function(value, option) { _setOption(option, value, init); }); } else { _setOption(option, value, init); } if (init) { // Normalize the list of required capabilities settings.required_features = normalizeCaps(plupload.extend({}, settings)); // Come up with the list of capabilities that can affect default mode in a multi-mode runtimes preferred_caps = normalizeCaps(plupload.extend({}, settings, { required_features: true })); } else if (reinitRequired) { self.trigger('Destroy'); initControls.call(self, settings, function(inited) { if (inited) { self.runtime = o.Runtime.getInfo(getRUID()).type; self.trigger('Init', { runtime: self.runtime }); self.trigger('PostInit'); } else { self.trigger('Error', { code : plupload.INIT_ERROR, message : plupload.translate('Init error.') }); } }); } } // Internal event handlers function onBeforeUpload(up, file) { // Generate unique target filenames if (up.settings.unique_names) { var matches = file.name.match(/\.([^.]+)$/), ext = "part"; if (matches) { ext = matches[1]; } file.target_name = file.id + '.' + ext; } } function onUploadFile(up, file) { var url = up.settings.url , chunkSize = up.settings.chunk_size , retries = up.settings.max_retries , features = up.features , offset = 0 , blob ; // make sure we start at a predictable offset if (file.loaded) { offset = file.loaded = chunkSize ? chunkSize * Math.floor(file.loaded / chunkSize) : 0; } function handleError() { if (retries-- > 0) { delay(uploadNextChunk, 1000); } else { file.loaded = offset; // reset all progress up.trigger('Error', { code : plupload.HTTP_ERROR, message : plupload.translate('HTTP Error.'), file : file, response : xhr.responseText, status : xhr.status, responseHeaders: xhr.getAllResponseHeaders() }); } } function uploadNextChunk() { var chunkBlob, formData, args = {}, curChunkSize; // make sure that file wasn't cancelled and upload is not stopped in general if (file.status !== plupload.UPLOADING || up.state === plupload.STOPPED) { return; } // send additional 'name' parameter only if required if (up.settings.send_file_name) { args.name = file.target_name || file.name; } if (chunkSize && features.chunks && blob.size > chunkSize) { // blob will be of type string if it was loaded in memory curChunkSize = Math.min(chunkSize, blob.size - offset); chunkBlob = blob.slice(offset, offset + curChunkSize); } else { curChunkSize = blob.size; chunkBlob = blob; } // If chunking is enabled add corresponding args, no matter if file is bigger than chunk or smaller if (chunkSize && features.chunks) { // Setup query string arguments if (up.settings.send_chunk_number) { args.chunk = Math.ceil(offset / chunkSize); args.chunks = Math.ceil(blob.size / chunkSize); } else { // keep support for experimental chunk format, just in case args.offset = offset; args.total = blob.size; } } xhr = new o.XMLHttpRequest(); // Do we have upload progress support if (xhr.upload) { xhr.upload.onprogress = function(e) { file.loaded = Math.min(file.size, offset + e.loaded); up.trigger('UploadProgress', file); }; } xhr.onload = function() { // check if upload made itself through if (xhr.status >= 400) { handleError(); return; } retries = up.settings.max_retries; // reset the counter // Handle chunk response if (curChunkSize < blob.size) { chunkBlob.destroy(); offset += curChunkSize; file.loaded = Math.min(offset, blob.size); up.trigger('ChunkUploaded', file, { offset : file.loaded, total : blob.size, response : xhr.responseText, status : xhr.status, responseHeaders: xhr.getAllResponseHeaders() }); // stock Android browser doesn't fire upload progress events, but in chunking mode we can fake them if (o.Env.browser === 'Android Browser') { // doesn't harm in general, but is not required anywhere else up.trigger('UploadProgress', file); } } else { file.loaded = file.size; } chunkBlob = formData = null; // Free memory // Check if file is uploaded if (!offset || offset >= blob.size) { // If file was modified, destory the copy if (file.size != file.origSize) { blob.destroy(); blob = null; } up.trigger('UploadProgress', file); file.status = plupload.DONE; up.trigger('FileUploaded', file, { response : xhr.responseText, status : xhr.status, responseHeaders: xhr.getAllResponseHeaders() }); } else { // Still chunks left delay(uploadNextChunk, 1); // run detached, otherwise event handlers interfere } }; xhr.onerror = function() { handleError(); }; xhr.onloadend = function() { this.destroy(); xhr = null; }; // Build multipart request if (up.settings.multipart && features.multipart) { xhr.open("post", url, true); // Set custom headers plupload.each(up.settings.headers, function(value, name) { xhr.setRequestHeader(name, value); }); formData = new o.FormData(); // Add multipart params plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) { formData.append(name, value); }); // Add file and send it formData.append(up.settings.file_data_name, chunkBlob); xhr.send(formData, { runtime_order: up.settings.runtimes, required_caps: up.settings.required_features, preferred_caps: preferred_caps }); } else { // if no multipart, send as binary stream url = plupload.buildUrl(up.settings.url, plupload.extend(args, up.settings.multipart_params)); xhr.open("post", url, true); xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // Binary stream header // Set custom headers plupload.each(up.settings.headers, function(value, name) { xhr.setRequestHeader(name, value); }); xhr.send(chunkBlob, { runtime_order: up.settings.runtimes, required_caps: up.settings.required_features, preferred_caps: preferred_caps }); } } blob = file.getSource(); // Start uploading chunks if (up.settings.resize.enabled && runtimeCan(blob, 'send_binary_string') && !!~o.inArray(blob.type, ['image/jpeg', 'image/png'])) { // Resize if required resizeImage.call(this, blob, up.settings.resize, function(resizedBlob) { blob = resizedBlob; file.size = resizedBlob.size; uploadNextChunk(); }); } else { uploadNextChunk(); } } function onUploadProgress(up, file) { calcFile(file); } function onStateChanged(up) { if (up.state == plupload.STARTED) { // Get start time to calculate bps startTime = (+new Date()); } else if (up.state == plupload.STOPPED) { // Reset currently uploading files for (var i = up.files.length - 1; i >= 0; i--) { if (up.files[i].status == plupload.UPLOADING) { up.files[i].status = plupload.QUEUED; calc(); } } } } function onCancelUpload() { if (xhr) { xhr.abort(); } } function onFileUploaded(up) { calc(); // Upload next file but detach it from the error event // since other custom listeners might want to stop the queue delay(function() { uploadNext.call(up); }, 1); } function onError(up, err) { if (err.code === plupload.INIT_ERROR) { up.destroy(); } // Set failed status if an error occured on a file else if (err.code === plupload.HTTP_ERROR) { err.file.status = plupload.FAILED; calcFile(err.file); // Upload next file but detach it from the error event // since other custom listeners might want to stop the queue if (up.state == plupload.STARTED) { // upload in progress up.trigger('CancelUpload'); delay(function() { uploadNext.call(up); }, 1); } } } function onDestroy(up) { up.stop(); // Purge the queue plupload.each(files, function(file) { file.destroy(); }); files = []; if (fileInputs.length) { plupload.each(fileInputs, function(fileInput) { fileInput.destroy(); }); fileInputs = []; } if (fileDrops.length) { plupload.each(fileDrops, function(fileDrop) { fileDrop.destroy(); }); fileDrops = []; } preferred_caps = {}; disabled = false; startTime = xhr = null; total.reset(); } // Default settings settings = { runtimes: o.Runtime.order, max_retries: 0, chunk_size: 0, multipart: true, multi_selection: true, file_data_name: 'file', filters: { mime_types: [], prevent_duplicates: false, max_file_size: 0 }, resize: { enabled: false, preserve_headers: true, crop: false }, send_file_name: true, send_chunk_number: true }; setOption.call(this, options, null, true); // Inital total state total = new plupload.QueueProgress(); // Add public methods plupload.extend(this, { /** * Unique id for the Uploader instance. * * @property id * @type String */ id : uid, uid : uid, // mOxie uses this to differentiate between event targets /** * Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED. * These states are controlled by the stop/start methods. The default value is STOPPED. * * @property state * @type Number */ state : plupload.STOPPED, /** * Map of features that are available for the uploader runtime. Features will be filled * before the init event is called, these features can then be used to alter the UI for the end user. * Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize. * * @property features * @type Object */ features : {}, /** * Current runtime name. * * @property runtime * @type String */ runtime : null, /** * Current upload queue, an array of File instances. * * @property files * @type Array * @see plupload.File */ files : files, /** * Object with name/value settings. * * @property settings * @type Object */ settings : settings, /** * Total progess information. How many files has been uploaded, total percent etc. * * @property total * @type plupload.QueueProgress */ total : total, /** * Initializes the Uploader instance and adds internal event listeners. * * @method init */ init : function() { var self = this, opt, preinitOpt, err; preinitOpt = self.getOption('preinit'); if (typeof(preinitOpt) == "function") { preinitOpt(self); } else { plupload.each(preinitOpt, function(func, name) { self.bind(name, func); }); } bindEventListeners.call(self); // Check for required options plupload.each(['container', 'browse_button', 'drop_element'], function(el) { if (self.getOption(el) === null) { err = { code : plupload.INIT_ERROR, message : plupload.translate("'%' specified, but cannot be found.") } return false; } }); if (err) { return self.trigger('Error', err); } if (!settings.browse_button && !settings.drop_element) { return self.trigger('Error', { code : plupload.INIT_ERROR, message : plupload.translate("You must specify either 'browse_button' or 'drop_element'.") }); } initControls.call(self, settings, function(inited) { var initOpt = self.getOption('init'); if (typeof(initOpt) == "function") { initOpt(self); } else { plupload.each(initOpt, function(func, name) { self.bind(name, func); }); } if (inited) { self.runtime = o.Runtime.getInfo(getRUID()).type; self.trigger('Init', { runtime: self.runtime }); self.trigger('PostInit'); } else { self.trigger('Error', { code : plupload.INIT_ERROR, message : plupload.translate('Init error.') }); } }); }, /** * Set the value for the specified option(s). * * @method setOption * @since 2.1 * @param {String|Object} option Name of the option to change or the set of key/value pairs * @param {Mixed} [value] Value for the option (is ignored, if first argument is object) */ setOption: function(option, value) { setOption.call(this, option, value, !this.runtime); // until runtime not set we do not need to reinitialize }, /** * Get the value for the specified option or the whole configuration, if not specified. * * @method getOption * @since 2.1 * @param {String} [option] Name of the option to get * @return {Mixed} Value for the option or the whole set */ getOption: function(option) { if (!option) { return settings; } return settings[option]; }, /** * Refreshes the upload instance by dispatching out a refresh event to all runtimes. * This would for example reposition flash/silverlight shims on the page. * * @method refresh */ refresh : function() { if (fileInputs.length) { plupload.each(fileInputs, function(fileInput) { fileInput.trigger('Refresh'); }); } this.trigger('Refresh'); }, /** * Starts uploading the queued files. * * @method start */ start : function() { if (this.state != plupload.STARTED) { this.state = plupload.STARTED; this.trigger('StateChanged'); uploadNext.call(this); } }, /** * Stops the upload of the queued files. * * @method stop */ stop : function() { if (this.state != plupload.STOPPED) { this.state = plupload.STOPPED; this.trigger('StateChanged'); this.trigger('CancelUpload'); } }, /** * Disables/enables browse button on request. * * @method disableBrowse * @param {Boolean} disable Whether to disable or enable (default: true) */ disableBrowse : function() { disabled = arguments[0] !== undef ? arguments[0] : true; if (fileInputs.length) { plupload.each(fileInputs, function(fileInput) { fileInput.disable(disabled); }); } this.trigger('DisableBrowse', disabled); }, /** * Returns the specified file object by id. * * @method getFile * @param {String} id File id to look for. * @return {plupload.File} File object or undefined if it wasn't found; */ getFile : function(id) { var i; for (i = files.length - 1; i >= 0; i--) { if (files[i].id === id) { return files[i]; } } }, /** * Adds file to the queue programmatically. Can be native file, instance of Plupload.File, * instance of mOxie.File, input[type="file"] element, or array of these. Fires FilesAdded, * if any files were added to the queue. Otherwise nothing happens. * * @method addFile * @since 2.0 * @param {plupload.File|mOxie.File|File|Node|Array} file File or files to add to the queue. * @param {String} [fileName] If specified, will be used as a name for the file */ addFile : function(file, fileName) { var self = this , queue = [] , filesAdded = [] , ruid ; function filterFile(file, cb) { var queue = []; o.each(self.settings.filters, function(rule, name) { if (fileFilters[name]) { queue.push(function(cb) { fileFilters[name].call(self, rule, file, function(res) { cb(!res); }); }); } }); o.inSeries(queue, cb); } /** * @method resolveFile * @private * @param {o.File|o.Blob|plupload.File|File|Blob|input[type="file"]} file */ function resolveFile(file) { var type = o.typeOf(file); // o.File if (file instanceof o.File) { if (!file.ruid && !file.isDetached()) { if (!ruid) { // weird case return false; } file.ruid = ruid; file.connectRuntime(ruid); } resolveFile(new plupload.File(file)); } // o.Blob else if (file instanceof o.Blob) { resolveFile(file.getSource()); file.destroy(); } // plupload.File - final step for other branches else if (file instanceof plupload.File) { if (fileName) { file.name = fileName; } queue.push(function(cb) { // run through the internal and user-defined filters, if any filterFile(file, function(err) { if (!err) { // make files available for the filters by updating the main queue directly files.push(file); // collect the files that will be passed to FilesAdded event filesAdded.push(file); self.trigger("FileFiltered", file); } delay(cb, 1); // do not build up recursions or eventually we might hit the limits }); }); } // native File or blob else if (o.inArray(type, ['file', 'blob']) !== -1) { resolveFile(new o.File(null, file)); } // input[type="file"] else if (type === 'node' && o.typeOf(file.files) === 'filelist') { // if we are dealing with input[type="file"] o.each(file.files, resolveFile); } // mixed array of any supported types (see above) else if (type === 'array') { fileName = null; // should never happen, but unset anyway to avoid funny situations o.each(file, resolveFile); } } ruid = getRUID(); resolveFile(file); if (queue.length) { o.inSeries(queue, function() { // if any files left after filtration, trigger FilesAdded if (filesAdded.length) { self.trigger("FilesAdded", filesAdded); } }); } }, /** * Removes a specific file. * * @method removeFile * @param {plupload.File|String} file File to remove from queue. */ removeFile : function(file) { var id = typeof(file) === 'string' ? file : file.id; for (var i = files.length - 1; i >= 0; i--) { if (files[i].id === id) { return this.splice(i, 1)[0]; } } }, /** * Removes part of the queue and returns the files removed. This will also trigger the FilesRemoved and QueueChanged events. * * @method splice * @param {Number} start (Optional) Start index to remove from. * @param {Number} length (Optional) Lengh of items to remove. * @return {Array} Array of files that was removed. */ splice : function(start, length) { // Splice and trigger events var removed = files.splice(start === undef ? 0 : start, length === undef ? files.length : length); // if upload is in progress we need to stop it and restart after files are removed var restartRequired = false; if (this.state == plupload.STARTED) { // upload in progress plupload.each(removed, function(file) { if (file.status === plupload.UPLOADING) { restartRequired = true; // do not restart, unless file that is being removed is uploading return false; } }); if (restartRequired) { this.stop(); } } this.trigger("FilesRemoved", removed); // Dispose any resources allocated by those files plupload.each(removed, function(file) { file.destroy(); }); if (restartRequired) { this.start(); } return removed; }, /** Dispatches the specified event name and its arguments to all listeners. @method trigger @param {String} name Event name to fire. @param {Object..} Multiple arguments to pass along to the listener functions. */ // override the parent method to match Plupload-like event logic dispatchEvent: function(type) { var list, args, result; type = type.toLowerCase(); list = this.hasEventListener(type); if (list) { // sort event list by priority list.sort(function(a, b) { return b.priority - a.priority; }); // first argument should be current plupload.Uploader instance args = [].slice.call(arguments); args.shift(); args.unshift(this); for (var i = 0; i < list.length; i++) { // Fire event, break chain if false is returned if (list[i].fn.apply(list[i].scope, args) === false) { return false; } } } return true; }, /** Check whether uploader has any listeners to the specified event. @method hasEventListener @param {String} name Event name to check for. */ /** Adds an event listener by name. @method bind @param {String} name Event name to listen for. @param {function} fn Function to call ones the event gets fired. @param {Object} [scope] Optional scope to execute the specified function in. @param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first */ bind: function(name, fn, scope, priority) { // adapt moxie EventTarget style to Plupload-like plupload.Uploader.prototype.bind.call(this, name, fn, priority, scope); }, /** Removes the specified event listener. @method unbind @param {String} name Name of event to remove. @param {function} fn Function to remove from listener. */ /** Removes all event listeners. @method unbindAll */ /** * Destroys Plupload instance and cleans after itself. * * @method destroy */ destroy : function() { this.trigger('Destroy'); settings = total = null; // purge these exclusively this.unbindAll(); } }); }; plupload.Uploader.prototype = o.EventTarget.instance; /** * Constructs a new file instance. * * @class File * @constructor * * @param {Object} file Object containing file properties * @param {String} file.name Name of the file. * @param {Number} file.size File size. */ plupload.File = (function() { var filepool = {}; function PluploadFile(file) { plupload.extend(this, { /** * File id this is a globally unique id for the specific file. * * @property id * @type String */ id: plupload.guid(), /** * File name for example "myfile.gif". * * @property name * @type String */ name: file.name || file.fileName, /** * File type, `e.g image/jpeg` * * @property type * @type String */ type: file.type || '', /** * File size in bytes (may change after client-side manupilation). * * @property size * @type Number */ size: file.size || file.fileSize, /** * Original file size in bytes. * * @property origSize * @type Number */ origSize: file.size || file.fileSize, /** * Number of bytes uploaded of the files total size. * * @property loaded * @type Number */ loaded: 0, /** * Number of percentage uploaded of the file. * * @property percent * @type Number */ percent: 0, /** * Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE. * * @property status * @type Number * @see plupload */ status: plupload.QUEUED, /** * Date of last modification. * * @property lastModifiedDate * @type {String} */ lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString(), // Thu Aug 23 2012 19:40:00 GMT+0400 (GET) /** * Returns native window.File object, when it's available. * * @method getNative * @return {window.File} or null, if plupload.File is of different origin */ getNative: function() { var file = this.getSource().getSource(); return o.inArray(o.typeOf(file), ['blob', 'file']) !== -1 ? file : null; }, /** * Returns mOxie.File - unified wrapper object that can be used across runtimes. * * @method getSource * @return {mOxie.File} or null */ getSource: function() { if (!filepool[this.id]) { return null; } return filepool[this.id]; }, /** * Destroys plupload.File object. * * @method destroy */ destroy: function() { var src = this.getSource(); if (src) { src.destroy(); delete filepool[this.id]; } } }); filepool[this.id] = file; } return PluploadFile; }()); /** * Constructs a queue progress. * * @class QueueProgress * @constructor */ plupload.QueueProgress = function() { var self = this; // Setup alias for self to reduce code size when it's compressed /** * Total queue file size. * * @property size * @type Number */ self.size = 0; /** * Total bytes uploaded. * * @property loaded * @type Number */ self.loaded = 0; /** * Number of files uploaded. * * @property uploaded * @type Number */ self.uploaded = 0; /** * Number of files failed to upload. * * @property failed * @type Number */ self.failed = 0; /** * Number of files yet to be uploaded. * * @property queued * @type Number */ self.queued = 0; /** * Total percent of the uploaded bytes. * * @property percent * @type Number */ self.percent = 0; /** * Bytes uploaded per second. * * @property bytesPerSec * @type Number */ self.bytesPerSec = 0; /** * Resets the progress to its initial values. * * @method reset */ self.reset = function() { self.size = self.loaded = self.uploaded = self.failed = self.queued = self.percent = self.bytesPerSec = 0; }; }; window.plupload = plupload; }(window, mOxie)); ;